浏览代码

Release 2023.0.0 (#1201)

* Assets/logos (#782)

* Added logo assets

* Added PNG 1260x640 with white border

Co-authored-by: 103filgualan <f.gualandi@crif.com>

* OPENSSH KeyReader for more keys (#614)

* OPENSSH KeyReader for more keys

Add support to parse OpenSSH Keys with ECDSA 256/384/521 and RSA.

https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key

Change-Id: Iaa9cce0f2522e5fee377a82cb252f81f0b7cc563

* Fix ED25519Key KeyLength

* Fix ED25519 PubKey-auth

LeadingZeros of BigInteger-Conversion have to be removed
before sending the Key.

* Add interface to SftpFile #120 (#812)

* Create ISftpFile interface. SftpFile sealed. Return ISftpFile from SftpClient instead of SftpFile. Make ISftpClient interface disposable.

Co-authored-by: Wojciech Swieboda <wswieboda@chathamfinancial.com>

* Start MessageListener with ThreadAbstraction.ExecuteThreadLongRunning (#902)

* Fix Thread pool exhaustion due to MessageListener running on ThreadPool
* Mark long running thread as background

* Add async support to SftpClient and SftpFileStream (#819)

* Add FEATURE_TAP and net472 target
* Add TAP async support to SftpClient and SftpFileStream
* Add async support to DnsAbstraction and SocketAbstraction
* Add async support to *Connector and refactor the hierarchy
* Add ConnectAsync to BaseClient

* Add CODEOWNERS file.

* Fix virus false-positive by Defender on Renci.SSHNet.Tests.dll (#867)

Co-authored-by: Pedro Fonseca <pfonseca@qti.qualcomm.com>

* Add unit tests for task-based asynchronous API (#906)

* Fix runtime and culture dependant tests.
* Set C# 7.3 in Tests.csproj to limit intellisense's suggestions under different targets
* Add SftpClientTest.*Async
* Add SftpFileStreamTest_OpenAsync_*
* Add SftpFileStreamTest_WriteAsync_*
* Add SftpFileStreamTest_ReadAsync_*
* Align AppVeyor script with Test project target frameworks

* correct 'Documenation' to 'Documentation' (#838)

in the documentation's window title

* Agent auth and Keygen (#794)

* Allow to set PrivateKeyFile Key directly
   So you can add your own Key-Classes to SSH.NET
* Add ED25519 ctor for just pub key part.
* Make ECDSA Key Bits accessible
   You cant export imported CngKeys. To be able to export them to agent or Key-Files make the private bits also accessible.
* Better NETFRAMEWORK vs NETSTANDARD handling
* Add Comment Property to Key
* Add IPrivateKeySource
  So Extension can add own PrivateKeyFiles, e.g. PuttyKeyFile.

* Use cryptographically secure random number generator.
Fixes CVE-2022-29245.

* Remove unused import.

* Add IBaseClient for BaseClient and ISftpClient to inherit from (#975)

Add IBaseClient for BaseClient and ISftpClient to inherit from

* fix typo (#999)

* Fix Seek Operations in SftpFileStream (#910)

* Fix offset operations in SftpFileStream.Seek
* Fix seek exception message and add default case for invalid seek origin
* Use named params when throwing ArgumentException
* Add tests for seeking from end of file

* Add back copyright to license. (#1060)

Fixes #1059.

* Removing old target frameworks (#1109)

Remove support for legacy / deprecated target frameworks while adding support for .NET 6.0 (and higher).
The supported target frameworks are now:
* .NETFramework 4.6.2 (and higher)
* .NET Standard 2.0
* .NET 6.0 (and higher)

* Remove old features [Part 1] (#1117)

Remove obsolete feature switches (now that we've remove support for legacy target frameworks) and remove corresponding conditional code.

* Remove FEATURE_DIRECTORYINFO_ENUMERATEFILES (#1119)

* Remove FEATURE_DIRECTORYINFO_ENUMERATEFILES
* Add exception documentation

* Fix some (lots of) issues reported by analyzers. (#1125)

Fix some (lots of) issues reported by analyzers.

* Round 2 of analyzer fixes and general cleanup. (#1132)

* Analyzer fixes round 3. (#1135)

* Replace Array<T>.Empty with Array.Empty<T>() (#1137)

* Replace IsNullOrWhiteSpace extension (#1142)

* Use License Expression for NuGet Package

licenseUrl is deprecated, see https://github.com/NuGet/Announcements/issues/32

* Integration tests

* Remove todos

* Update CODEOWNERS

* Use correct SSH.NET

* ListDirectoryAsync return IAsyncEnumerable (#1126)

* ListDirectoryAsync return IAsyncEnumerable

* Fix documentation

* Update README.md

* Fix

* Add Sftp ListDirectoryAsync test

* Revert

* Integration tests for ListDirectoryAsync with IAsyncEnumerable

* Fix the assembly resolution build warning (#1165)

* Delete performance/longrunning tests (#1143)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Move Integration tests (#1173)

* Renci.SshNet.IntegrationTests

* Renci.SshNet.TestTools.OpenSSH

* Move integration tests to main repo

* Move old tests to new integration tests

* Move old integration tests to new integration tests

* Move more tests

* Move authentication tests

* Move SshClientTests

* Fix some tests

* Remove duplicated test

* Poc of ProcessDisruptor

* Rename

* Some fixes

* Remove performance tests

* Small improvements

* Add a benchmarks project (#1151)

* Add a benchmarks project

* Small improvements

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Use ExceptionDispatchInfo to retain call stack in Session.WaitOnHandle() (#936)

* Use ExceptionDispatchInfo to retain call stack in Session.WaitOnHandle()

* merge

* Update src/Renci.SshNet/Session.cs

Co-authored-by: Rob Hague <rob.hague00@gmail.com>

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>
Co-authored-by: Rob Hague <rob.hague00@gmail.com>

* Support SHA256 fingerprints for host key validation (#1098)

* Add tests for HostKeyEventArgs

* Add SHA256 fingerprint support

* Add support for RSA SHA-2 public key algorithms (#1177)

* Abstract out the hash algorithm from RsaDigitalSignature

* Add integration tests

* Add DigitalSignature property to KeyHostAlgorithm

* Add IHostAlgorithmsProvider interface

* Verify the host signature

* Fix HostKeyEventArgsTest after merge

* Remove PubkeyAcceptedAlgorithms ssh-rsa

* Add test coverage for RSA keys in PrivateKeyFile

* Obsolete IPrivateKeySource

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Improvements after #1177 (#1180)

* Use ExceptionDispatchInfo in more places (#1182)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Try to "fix" the flaky test (#1185)

* Enable DSA tests (#1181)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* FingerPrints (#1186)

* Use OS-agnostic socket error codes to allow tests run on different OSes (#1179)

SocketErrorCode is OS agnostic, ErrorCode is OS specific.

On Windows ErrorCode = (int) SocketErrorCode, but on Mac and Unix it is not.

For example ExitCode for HostNotFound (11001) on Windows is 11001, on Mac & Unix is -131073. So testing for ExitCode == 11001 fails on Mac & Unix.

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Fix for channel session semaphore from thread blocking (#1071)

* Merging fix from @clivetong into our own SSH.NET fork
- The following article describes some of the issues with the double check lock that we have seen issues with: https://www.sudhanshutheone.com/posts/double-check-lock-csharp

* Merging fix from @clivetong into our own SSH.NET fork
- The following article describes some of the issues with the double check lock that we have seen issues with: https://www.sudhanshutheone.com/posts/double-check-lock-csharp

* Update Channel to fix AppVeyor failure (field should be readonly)

* Update ISftpClient for #120 (#1193)

* Implement set last write and access time (#1194)

* Add/migrate hmac+cipher integration tests (#1189)

* Add/migrate hmac+cipher integration tests

* fix integration tests

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Update tests for SetLastAccessTime(Utc) to also verify the time component and the Kind of the DateTime value returned by GetLastAccessTime(Utc). (#1198)

---------

Co-authored-by: Filippo Gualandi <filippo.gualandi@gmail.com>
Co-authored-by: 103filgualan <f.gualandi@crif.com>
Co-authored-by: Stefan Rinkes <darinkes@users.noreply.github.com>
Co-authored-by: wxtsxt <wojciech.swieboda@gmail.com>
Co-authored-by: Wojciech Swieboda <wswieboda@chathamfinancial.com>
Co-authored-by: Igor Milavec <igor.milavec@gmail.com>
Co-authored-by: drieseng <gert.driesen@telenet.be>
Co-authored-by: Pedro Fonseca <pbfonseca@gmail.com>
Co-authored-by: Pedro Fonseca <pfonseca@qti.qualcomm.com>
Co-authored-by: Maximiliano Jabase <maxijabase@gmail.com>
Co-authored-by: Owen Krueger <37021716+Owen-Krueger@users.noreply.github.com>
Co-authored-by: Masuri <psh0258@gmail.com>
Co-authored-by: LemonPi314 <49930425+LemonPi314@users.noreply.github.com>
Co-authored-by: Gert Driesen <gertdriesen@msn.com>
Co-authored-by: Rob Hague <rob.hague00@gmail.com>
Co-authored-by: Rob Hague <rh@johnstreetcapital.com>
Co-authored-by: Marius Thesing <marius.thesing@gmail.com>
Co-authored-by: Dāvis Mošenkovs <davikovs@gmail.com>
Co-authored-by: Dmitry Tsarevich <dimhotepus@users.noreply.github.com>
Co-authored-by: Patrick Yates <114094360+patrick-yates-redgate@users.noreply.github.com>
Wojciech Nagórski 2 年之前
父节点
当前提交
2bbfee4a37
共有 100 个文件被更改,包括 16716 次插入324 次删除
  1. 1246 0
      .editorconfig
  2. 3 0
      .gitignore
  3. 1 0
      CODEOWNERS
  4. 44 0
      Directory.Build.props
  5. 2 0
      LICENSE
  6. 8 39
      README.md
  7. 6 6
      appveyor.yml
  8. 22 97
      build/build.proj
  9. 6 25
      build/nuget/SSH.NET.nuspec
  10. 14 23
      build/sandcastle/SSH.NET.shfbproj
  11. 二进制
      images/logo/png/SS-NET-1280x640.png
  12. 0 0
      src/Data/Key.ECDSA.Encrypted.txt
  13. 0 0
      src/Data/Key.ECDSA.txt
  14. 0 0
      src/Data/Key.ECDSA384.Encrypted.txt
  15. 0 0
      src/Data/Key.ECDSA384.txt
  16. 0 0
      src/Data/Key.ECDSA521.Encrypted.txt
  17. 0 0
      src/Data/Key.ECDSA521.txt
  18. 9 0
      src/Data/Key.OPENSSH.ECDSA.Encrypted.txt
  19. 9 0
      src/Data/Key.OPENSSH.ECDSA.txt
  20. 11 0
      src/Data/Key.OPENSSH.ECDSA384.Encrypted.txt
  21. 10 0
      src/Data/Key.OPENSSH.ECDSA384.txt
  22. 12 0
      src/Data/Key.OPENSSH.ECDSA521.Encrypted.txt
  23. 12 0
      src/Data/Key.OPENSSH.ECDSA521.txt
  24. 0 0
      src/Data/Key.OPENSSH.ED25519.Encrypted.txt
  25. 0 0
      src/Data/Key.OPENSSH.ED25519.txt
  26. 28 0
      src/Data/Key.OPENSSH.RSA.Encrypted.txt
  27. 27 0
      src/Data/Key.OPENSSH.RSA.txt
  28. 0 0
      src/Data/Key.RSA.Encrypted.Aes.128.CBC.12345.txt
  29. 0 0
      src/Data/Key.RSA.Encrypted.Aes.192.CBC.12345.txt
  30. 0 0
      src/Data/Key.RSA.Encrypted.Aes.256.CBC.12345.txt
  31. 0 0
      src/Data/Key.RSA.Encrypted.Des.CBC.12345.txt
  32. 0 0
      src/Data/Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt
  33. 0 0
      src/Data/Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt
  34. 0 0
      src/Data/Key.RSA.txt
  35. 0 0
      src/Data/Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt
  36. 0 0
      src/Data/Key.SSH2.DSA.txt
  37. 0 0
      src/Data/Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt
  38. 0 0
      src/Data/Key.SSH2.RSA.txt
  39. 66 0
      src/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs
  40. 27 0
      src/Renci.SshNet.Benchmarks/Program.cs
  41. 31 0
      src/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj
  42. 38 0
      src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs
  43. 49 0
      src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs
  44. 41 0
      src/Renci.SshNet.Benchmarks/Security/Cryptography/ED25519DigitalSignatureBenchmarks.cs
  45. 32 0
      src/Renci.SshNet.IntegrationTests/.editorconfig
  46. 1 0
      src/Renci.SshNet.IntegrationTests/.gitignore
  47. 23 0
      src/Renci.SshNet.IntegrationTests/App.config
  48. 84 0
      src/Renci.SshNet.IntegrationTests/AuthenticationMethodFactory.cs
  49. 427 0
      src/Renci.SshNet.IntegrationTests/AuthenticationTests.cs
  50. 81 0
      src/Renci.SshNet.IntegrationTests/CipherTests.cs
  51. 32 0
      src/Renci.SshNet.IntegrationTests/Common/ArrayBuilder.cs
  52. 393 0
      src/Renci.SshNet.IntegrationTests/Common/AsyncSocketListener.cs
  53. 11 0
      src/Renci.SshNet.IntegrationTests/Common/DateTimeAssert.cs
  54. 12 0
      src/Renci.SshNet.IntegrationTests/Common/DateTimeExtensions.cs
  55. 30 0
      src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs
  56. 255 0
      src/Renci.SshNet.IntegrationTests/Common/Socks5Handler.cs
  57. 462 0
      src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs
  58. 14 0
      src/Renci.SshNet.IntegrationTests/Credential.cs
  59. 50 0
      src/Renci.SshNet.IntegrationTests/Dockerfile
  60. 75 0
      src/Renci.SshNet.IntegrationTests/HmacTests.cs
  61. 115 0
      src/Renci.SshNet.IntegrationTests/HostConfig.cs
  62. 80 0
      src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs
  63. 23 0
      src/Renci.SshNet.IntegrationTests/HostKeyFile.cs
  64. 10 0
      src/Renci.SshNet.IntegrationTests/IConnectionInfoFactory.cs
  65. 206 0
      src/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs
  66. 36 0
      src/Renci.SshNet.IntegrationTests/LinuxAdminConnectionFactory.cs
  67. 62 0
      src/Renci.SshNet.IntegrationTests/LinuxVMConnectionFactory.cs
  68. 158 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ForwardedPortLocalTest.cs
  69. 336 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs
  70. 10 18
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs
  71. 72 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs
  72. 71 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.DeleteDirectory.cs
  73. 11 24
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Download.cs
  74. 263 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs
  75. 6 13
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFile.cs
  76. 56 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFileAsync.cs
  77. 8 14
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.SynchronizeDirectories.cs
  78. 67 65
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs
  79. 68 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.cs
  80. 120 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs
  81. 545 0
      src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs
  82. 102 0
      src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs
  83. 11 0
      src/Renci.SshNet.IntegrationTests/Program.cs
  84. 258 0
      src/Renci.SshNet.IntegrationTests/RemoteSshd.cs
  85. 66 0
      src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj
  86. 41 0
      src/Renci.SshNet.IntegrationTests/ScpClientTests.cs
  87. 2379 0
      src/Renci.SshNet.IntegrationTests/ScpTests.cs
  88. 102 0
      src/Renci.SshNet.IntegrationTests/SftpClientTests.cs
  89. 6360 0
      src/Renci.SshNet.IntegrationTests/SftpTests.cs
  90. 32 0
      src/Renci.SshNet.IntegrationTests/SshClientTests.cs
  91. 41 0
      src/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs
  92. 36 0
      src/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs
  93. 972 0
      src/Renci.SshNet.IntegrationTests/SshTests.cs
  94. 80 0
      src/Renci.SshNet.IntegrationTests/TestBase.cs
  95. 19 0
      src/Renci.SshNet.IntegrationTests/TestInitializer.cs
  96. 76 0
      src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs
  97. 85 0
      src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs
  98. 16 0
      src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs
  99. 8 0
      src/Renci.SshNet.IntegrationTests/Users.cs
  100. 5 0
      src/Renci.SshNet.IntegrationTests/Usings.cs

+ 1246 - 0
.editorconfig

@@ -0,0 +1,1246 @@
+// Avoid looking for .editorconfig files in parent directories
+root=true
+
+[*]
+
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+tab_width = 4
+end_of_line = crlf
+
+[*.cs]
+
+#### Sonar rules ####
+
+# S101: Types should be named in PascalCase
+# https://rules.sonarsource.com/csharp/RSPEC-101
+#
+# TODO: Remove this when code has been updated!
+dotnet_diagnostic.S101.severity = none
+
+# S112: General exceptions should never be thrown
+# https://rules.sonarsource.com/csharp/RSPEC-112
+#
+# This is a duplicate of CA2201 and MA0012.
+dotnet_diagnostic.S112.severity = none
+
+# S907: Remove use of 'goto'
+# https://rules.sonarsource.com/csharp/RSPEC-907
+#
+# Limited use of 'goto' is accepted when performance is critical.
+dotnet_diagnostic.S907.severity = none
+
+# S1066: Collapsible "if" statements should be merged
+# https://rules.sonarsource.com/csharp/RSPEC-1066
+#
+dotnet_diagnostic.S1066.severity = none
+
+# S1075: URIs should not be hardcoded
+# https://rules.sonarsource.com/csharp/RSPEC-1075
+#
+# The rule reports false positives for XML namespaces.
+dotnet_diagnostic.S1075.severity = none
+
+# S1104: Fields should not have public accessibility
+# https://rules.sonarsource.com/csharp/RSPEC-1104
+#
+# This is a duplicate of SA1401 and CA1051.
+dotnet_diagnostic.S1104.severity = none
+
+# S1125: Boolean literals should not be redundant
+# https://rules.sonarsource.com/csharp/RSPEC-1125
+#
+# This is a duplicate of MA0073.
+dotnet_diagnostic.S1125.severity = none
+
+# S1135: Track uses of "TODO" tags
+#
+# This is a duplicate of MA0026.
+dotnet_diagnostic.S1135.severity = none
+
+# S1168: Empty arrays and collections should be returned instead of null
+# https://rules.sonarsource.com/csharp/RSPEC-1168
+#
+# We sometimes return null to avoid allocating an empty List<T>.
+dotnet_diagnostic.S1168.severity = none
+
+# S1172: Unused method parameters should be removed
+# https://rules.sonarsource.com/csharp/RSPEC-1172
+#
+# This is a duplicate of IDE0060.
+dotnet_diagnostic.S1172.severity = none
+
+# S1481: Unused local variables should be removed
+# https://rules.sonarsource.com/csharp/RSPEC-1481
+#
+# This is a duplicate of IDE0059.
+dotnet_diagnostic.S1481.severity = none
+
+# S2259: Null pointers should not be dereferenced
+# https://rules.sonarsource.com/csharp/RSPEC-2259
+#
+# The analysis is not precise enough, leading to false positives.
+dotnet_diagnostic.S2259.severity = none
+
+# S2445: Blocks should be synchronized on read-only fields
+# https://rules.sonarsource.com/csharp/RSPEC-2445
+#
+# This is a (partial) duplicate of MA0064.
+dotnet_diagnostic.S2445.severity = none
+
+# S2551: Shared resources should not be used for locking
+# https://rules.sonarsource.com/csharp/RSPEC-2551
+#
+# This is a duplicate of CA2002, and partial duplicate of MA0064.
+dotnet_diagnostic.S2551.severity = none
+
+# S2583: Conditionally executed code should be reachable
+# https://rules.sonarsource.com/csharp/RSPEC-2583
+#
+# This rule produces false errors in, for example, for loops.
+#dotnet_diagnostic.S2583.severity = none
+
+# S2699: Tests should include assertions
+# https://rules.sonarsource.com/csharp/RSPEC-2699
+#
+# Sometimes you want a test in which you invoke a method and just want to verify that it does not throw.
+# For example:
+# [TestMethod]
+# public void InvokeDisposeWithoutNotifyObjectShouldNotThrow()
+# {
+#     _timer.Dispose();
+# }
+dotnet_diagnostic.S2699.severity = none
+
+# S2933: Fields that are only assigned in the constructor should be "readonly"
+# https://rules.sonarsource.com/csharp/RSPEC-2933
+#
+# This is a duplicate of IDE0044, but IDE0044 is not reported when targeting .NET Framework 4.8.
+dotnet_diagnostic.S2933.severity = none
+
+# S2971: "IEnumerable" LINQs should be simplified
+# https://rules.sonarsource.com/csharp/RSPEC-2971
+#
+# This is a duplicate of MA0020.
+dotnet_diagnostic.S2971.severity = none
+
+# S3218: Inner class members should not shadow outer class "static" or type members
+# https://rules.sonarsource.com/csharp/RSPEC-3218
+#
+# This is rather harmless.
+dotnet_diagnostic.S3218.severity = none
+
+# S3267: Loops should be simplified with "LINQ" expressions
+# https://rules.sonarsource.com/csharp/RSPEC-3267
+#
+# LINQ is the root of all evil :p
+dotnet_diagnostic.S3267.severity = none
+
+# S3376: Attribute, EventArgs, and Exception type names should end with the type being extended
+# https://rules.sonarsource.com/csharp/RSPEC-3376
+#
+# This is a partial duplicate of MA0058. If we enable the Sonar in all repositories, we should
+# consider enabling S3376 in favor of MA0058.
+dotnet_diagnostic.S3376.severity = none
+
+# S3871: Exception types should be "public"
+# https://rules.sonarsource.com/csharp/RSPEC-3871
+#
+# This is a duplicate of CA1064.
+dotnet_diagnostic.S3871.severity = none
+
+# S3925: "ISerializable" should be implemented correctly
+# https://rules.sonarsource.com/csharp/RSPEC-3925
+#
+# This is a duplicate of CA2229.
+dotnet_diagnostic.S3925.severity = none
+
+# S3928: Parameter names used into ArgumentException constructors should match an existing one
+# https://rules.sonarsource.com/csharp/RSPEC-3928
+#
+# This is a duplicate of MA0015.
+dotnet_diagnostic.S3928.severity = none
+
+# S3998: Threads should not lock on objects with weak identity
+# https://rules.sonarsource.com/csharp/RSPEC-3998
+#
+# This is a duplicate of CA2002, and partial duplicate of MA0064.
+dotnet_diagnostic.S3998.severity = none
+
+# S4456: Parameter validation in yielding methods should be wrapped
+# https://rules.sonarsource.com/csharp/RSPEC-4456
+#
+# This is a duplicate of MA0050.
+dotnet_diagnostic.S4456.severity = none
+
+# S4487: Unread "private" fields should be removed
+# https://rules.sonarsource.com/csharp/RSPEC-4487
+#
+# This is a duplicate of IDE0052.
+dotnet_diagnostic.S4487.severity = none
+
+# S4581: "new Guid()" should not be used
+# https://rules.sonarsource.com/csharp/RSPEC-4581
+#
+# This is a partial duplicate of MA0067, and we do not want to report the use of 'default' for a Guid as error.
+dotnet_diagnostic.S4581.severity = none
+
+#### StyleCop rules ####
+
+# SA1003: Symbols must be spaced correctly
+#
+# When enabled, a diagnostic is produced when there's a space after a cast.
+# For example:
+# var x = (int) z;
+dotnet_diagnostic.SA1003.severity = none
+
+# SA1008: Opening parenthesis should not be preceded by a space
+#
+# When enabled, a diagnostic is produce when a cast precedes braces.
+# For example:
+# (long) (a * b)
+dotnet_diagnostic.SA1008.severity = none
+
+# SA1009: Closing parenthesis should not be followed by a space
+#
+# When enabled, a diagnostic is produced when there's a space after a cast.
+# For example:
+# var x = (int) z;
+dotnet_diagnostic.SA1009.severity = none
+
+# SA1101: Prefix local calls with this
+dotnet_diagnostic.SA1101.severity = none
+
+# SA1116: Split parameters must start on line after declaration
+#
+# When enabled, a diagnostic is produced when the first parameter is on the same line as the method or constructor.
+# For example:
+# arrayBuilder.Add(new StatisticsCallInfo(callsByType.Key,
+#                                         callsForType.Count);
+dotnet_diagnostic.SA1116.severity = none
+
+# SA1200: Using directives must be placed correctly
+#
+# This is already verified by the .NET compiler platform analyzers (csharp_using_directive_placement option and IDE0065 rule).
+dotnet_diagnostic.SA1200.severity = none
+
+# SA1201: Elements must appear in the correct order
+dotnet_diagnostic.SA1201.severity = none
+
+# SA1206: Modifiers are not ordered
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1206.md
+#
+# This is a duplicate of IDE0036, except that it cannot be configured and expects the required modifier to be before the
+# accessibility modifier.
+dotnet_diagnostic.SA1206.severity = none
+
+# SA1309: Field names must not begin with underscore
+dotnet_diagnostic.SA1309.severity = none
+
+# SA1405: Debug.Assert should provide message text
+#
+# To be discussed if we want to enable this.
+dotnet_diagnostic.SA1405.severity = none
+
+# SA1413: Use trailing comma in multi-line initializers
+dotnet_diagnostic.SA1413.severity = none
+
+# SA1503: Braces should not be omitted
+#
+# This is a duplicate of IDE0011.
+dotnet_diagnostic.SA1503.severity = none
+
+# SA1516: Elements must be separated by blank line
+#
+# When enabled, a diagnostic is produced for properties with both a get and set accessor.
+# For example:
+# public bool EnableStatistics
+# {
+#     get
+#     {
+#         return _enableStatistics;
+#     }
+#     set
+#     {
+#         _enableStatistics = value;
+#     }
+# }
+dotnet_diagnostic.SA1516.severity = none
+
+# SA1520: Use braces consistently
+#
+# Since we always require braces (configured via csharp_prefer_braces and reported as IDE0011), it does not make sense to check if braces
+# are used consistently.
+dotnet_diagnostic.SA1520.severity = none
+
+# SA1633: File must have header
+#
+# We do not use file headers.
+dotnet_diagnostic.SA1633.severity = none
+
+# SA1648: <inheritdoc> must be used with inheriting class
+#
+# This rule is disabled by default, hence we need to explicitly enable it.
+dotnet_diagnostic.SA1648.severity = error
+
+# SX1101: Do not prefix local members with 'this.'
+#
+# This rule is disabled by default, hence we need to explicitly enable it.
+dotnet_diagnostic.SX1101.severity = error
+
+# SX1309: Field names must begin with underscore
+#
+# This rule is disabled by default, hence we need to explicitly enable it.
+dotnet_diagnostic.SX1309.severity = error
+
+# SX1309S: Static field names must begin with underscore
+#
+# This rule is disabled by default, hence we need to explicitly enable it.
+dotnet_diagnostic.SX1309S.severity = error
+
+#### Meziantou.Analyzer rules ####
+
+# MA0002: Use an overload that has a IEqualityComparer<string> or IComparer<string> parameter
+#
+# In .NET (Core) there have been quite some optimizations for EqualityComparer<T>.Default (eg. https://github.com/dotnet/coreclr/pull/14125)
+# and Comparer<T>.Default (eg. https://github.com/dotnet/runtime/pull/48160).
+#
+# We'll have to verify impact on performance before we decide to use specific comparers (eg. StringComparer.InvariantCultureIgnoreCase).
+dotnet_diagnostic.MA0002.severity = none
+
+# MA0006: Use string.Equals instead of Equals operator
+#
+# We almost always want ordinal comparison, and using the explicit overload adds a little overhead
+# and is more chatty.
+dotnet_diagnostic.MA0006.severity = none
+
+# MA0007: Add a comma after the last value
+#
+# We do not add a comma after the last value in multi-line initializers.
+# For example:
+# public enum Sex
+# {
+#     Male = 1,
+#     Female = 2 // No comma here
+# }
+#
+# Note:
+# This is a duplicate of SA1413.
+dotnet_diagnostic.MA0007.severity = none
+
+# MA0009: Add regex evaluation timeout
+#
+# We do not see a need guard our regex's against a DOS attack.
+dotnet_diagnostic.MA0009.severity = none
+
+# MA0011: IFormatProvider is missing
+#
+# Also report diagnostic in ToString(...) methods
+MA0011.exclude_tostring_methods = false
+
+# MA0012: Do not raise reserved exception type
+#
+# This is a duplicate of CA2201.
+dotnet_diagnostic.MA0012.severity = none
+
+# MA0014: Do not raise System.ApplicationException type
+#
+# This is a duplicate of CA2201.
+dotnet_diagnostic.MA0014.severity = none
+
+# MA0016: Prefer returning collection abstraction instead of implementation
+#
+# This is a duplicate of CA1002.
+dotnet_diagnostic.MA0016.severity = none
+
+# MA0018: Do not declare static members on generic types
+#
+# This is a duplicate of CA1000.
+dotnet_diagnostic.MA0018.severity = none
+
+# MA0021: Use StringComparer.GetHashCode instead of string.GetHashCode
+#
+# No strong need for this, and may negatively affect performance.
+dotnet_diagnostic.MA0021.severity = none
+
+# MA0031: Optimize Enumerable.Count() usage
+# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0031.md
+#
+# The proposed code is less readable.
+#
+# For example:
+#
+# the following code fragment:
+# enumerable.Count() > 10;
+#
+# would become:
+# enumerable.Skip(10).Any();
+dotnet_diagnostic.MA0031.severity = none
+
+# MA0036: Make class static
+#
+# This is a partial duplicate of CA1052.
+dotnet_diagnostic.MA0036.severity = none
+
+# MA0038: Make method static
+#
+# This is a partial duplicate of, and deprecated in favor of, CA1822.
+dotnet_diagnostic.MA0038.severity = none
+
+# MA0041: Make property static
+#
+# This is a partial duplicate of, and deprecated in favor of, CA1822.
+dotnet_diagnostic.MA0041.severity = none
+
+# MA0048: File name must match type name
+#
+# This is a duplicate of SA1649.
+dotnet_diagnostic.MA0048.severity = none
+
+# MA0049: Type name should not match containing namespace
+#
+# This is a duplicate of CA1724
+dotnet_diagnostic.MA0049.severity = none
+
+# MA0051: Method is too long
+#
+# We do not want to limit the number of lines or statements per method.
+dotnet_diagnostic.MA0051.severity = none
+
+# MA0053: Make class sealed
+#
+# Also report diagnostic for public types.
+MA0053.public_class_should_be_sealed = true
+
+# MA0053: Make class sealed
+#
+# Also report diagnostic for types that derive from System.Exception.
+MA0053.exceptions_should_be_sealed = true
+
+# MA0053: Make class sealed
+#
+# Also report diagnostic for types that define (new) virtual members.
+MA0053.class_with_virtual_member_shoud_be_sealed = true
+
+# MA0112: Use 'Count > 0' instead of 'Any()'
+#
+# This rule is disabled by default, hence we need to explicitly enable it.
+dotnet_diagnostic.MA0112.severity = error
+
+#### .NET Compiler Platform code quality rules ####
+
+# CA1002: Do not expose generic lists
+#
+# For performance reasons - to avoid interface dispatch - we expose generic lists
+# instead of a base class or interface.
+dotnet_diagnostic.CA1002.severity = none
+
+# CA1008: Enums should have zero value
+#
+# TODO: To be discussed. Having a zero value offers a performance advantage.
+dotnet_diagnostic.CA1008.severity = none
+
+# CA1014: Mark assemblies with CLSCompliantAttribute
+#
+# This rule is disabled by default, hence we need to explicitly enable it.
+#
+# Even when enabled, this diagnostic does not appear to be reported for assemblies without CLSCompliantAttribute.
+# We reported this issue as https://github.com/dotnet/roslyn-analyzers/issues/6563.
+dotnet_diagnostic.CA1014.severity = error
+
+# CA1051: Do not declare visible instance fields
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1051
+#
+# This is a duplicate of S1104 and SA1401.
+dotnet_diagnostic.CA1051.severity = none
+
+# CA1052: Static holder types should be Static or NotInheritable
+#
+# By default, this diagnostic is only reported for public types.
+dotnet_code_quality.CA1052.api_surface = all
+
+# CA1303: Do not pass literals as localized parameters
+#
+# We don't care about localization.
+dotnet_diagnostic.CA1303.severity = none
+
+# CA1305: Specify IFormatProvider
+#
+# This is a an equivalent of MA0011, except that it does not report a diagnostic for the use of
+# DateTime.TryParse(string s, out DateTime result).
+#
+# Submitted https://github.com/dotnet/roslyn-analyzers/issues/6096 to fix CA1305.
+dotnet_diagnostic.CA1305.severity = none
+
+# CA1510: Use ArgumentNullException throw helper
+#
+# This is only available in .NET 6.0 and higher. We'd need to use conditional compilation to only
+# use these throw helper when targeting a framework that supports it.
+dotnet_diagnostic.CA1510.severity = none
+
+# CA1725: Parameter names should match base declaration
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725
+#
+# This is a duplicate of S927, but contains at least one bug:
+# https://github.com/dotnet/roslyn-analyzers/issues/6461
+#
+# Since we do not enable any of the Sonar rules by default, we'll leave CA1725 enabled.
+dotnet_diagnostic.CA1725.severity = error
+
+# CA1819: Properties should not return arrays
+#
+# Arrays offer better performance than collections.
+dotnet_diagnostic.CA1819.severity = none
+
+# CA1828: Mark members as static
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822
+#
+# Documentation does not mention which API surface(s) this rule runs on, so we explictly configure it.
+dotnet_code_quality.CA1828.api_surface = all
+
+# CA1852: Seal internal types
+#
+# Similar to MA0053, but does not support public types and types that define (new) virtual members.
+dotnet_diagnostic.CA1852.severity = none
+
+# CA1859: Change return type for improved performance
+#
+# By default, this diagnostic is only reported for private members.
+dotnet_code_quality.CA1859.api_surface = all
+
+# CA2208: Instantiate argument exceptions correctly
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2208
+#
+# This is similar to, but less powerful than, MA0015.
+dotnet_diagnostic.CA2208.severity = none
+
+#### Roslyn IDE analyser rules ####
+
+# IDE0032: Use auto-implemented property
+#
+# For performance reasons, we do not always want to enforce the use of
+# auto-implemented properties.
+dotnet_diagnostic.IDE0032.severity = suggestion
+
+# IDE0045: Use conditional expression for assignment
+#
+# This does not always result in cleaner/clearer code.
+dotnet_diagnostic.IDE0045.severity = none
+
+# IDE0046: Use conditional expression for return
+# 
+# Using a conditional expression is not always a clear win for readability.
+#
+# Configured using 'dotnet_style_prefer_conditional_expression_over_return'
+dotnet_diagnostic.IDE0046.severity = suggestion
+
+# IDE0047: Remove unnecessary parentheses
+# 
+# Removing "unnecessary" parentheses is not always a clear win for readability.
+dotnet_diagnostic.IDE0047.severity = suggestion
+
+# IDE0055: Fix formatting
+#
+# When enabled, diagnostics are reported for indented object initializers.
+# For example:
+# _content = new Person
+#     {
+#         Name = "\u13AAlarm"
+#     };
+#
+# There are no settings to configure this correctly, unless https://github.com/dotnet/roslyn/issues/63256 (or similar) is ever implemented.
+dotnet_diagnostic.IDE0055.severity = none
+
+# IDE0130: Namespace does not match folder structure
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0130
+#
+# TODO: Remove when https://github.com/sshnet/SSH.NET/issues/1129 is fixed
+dotnet_diagnostic.IDE0130.severity = none
+
+# IDE0270: Null check can be simplified
+# 
+# var inputPath = originalDossierPathList.Find(x => x.id == updatedPath.id);
+# if (inputPath is null)
+# {
+#     throw new PcsException($"Path id ({updatedPath.id}) unknown in PCS for dossier id {dossierFromTs.dossier.id}", updatedPath.id);
+# }
+#
+# We do not want to modify the code using a null coalescing operator:
+#
+# var inputPath = originalDossierPathList.Find(x => x.id == updatedPath.id) ?? throw new PcsException($"Path id ({updatedPath.id}) unknown in PCS for dossier id {dossierFromTs.dossier.id}", updatedPath.id);
+dotnet_diagnostic.IDE0270.severity = none
+
+#### .NET Compiler Platform code style rules ####
+
+### Language rules ###
+
+## Modifier preferences
+
+dotnet_style_require_accessibility_modifiers = true
+dotnet_style_readonly_field = true
+csharp_prefer_static_local_function = true
+
+## Parentheses preferences
+
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+
+# Expression-level preferences
+
+dotnet_style_object_initializer = true
+csharp_style_inlined_variable_declaration = true
+dotnet_style_collection_initializer = true
+dotnet_style_prefer_auto_properties = true
+dotnet_style_explicit_tuple_names = true
+csharp_prefer_simple_default_expression = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+csharp_style_prefer_local_over_anonymous_function = true
+csharp_style_deconstructed_variable_declaration = false
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_compound_assignment = true
+csharp_style_prefer_index_operator = false
+csharp_style_prefer_range_operator = false
+dotnet_style_prefer_simplified_interpolation = false
+dotnet_style_prefer_simplified_boolean_expressions = true
+csharp_style_implicit_object_creation_when_type_is_apparent = false
+csharp_style_prefer_tuple_swap = false
+
+# Namespace declaration preferences
+
+csharp_style_namespace_declarations = block_scoped
+
+# Null-checking preferences
+
+csharp_style_throw_expression = false
+dotnet_style_coalesce_expression = true
+dotnet_style_null_propagation = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_conditional_delegate_call = true
+
+# 'var' preferences
+
+csharp_style_var_for_built_in_types = true
+csharp_style_var_when_type_is_apparent = true
+csharp_style_var_elsewhere = true
+
+# Expression-bodies members
+
+csharp_style_expression_bodied_methods = false
+csharp_style_expression_bodied_constructors = false
+csharp_style_expression_bodied_operators = false
+csharp_style_expression_bodied_properties = false
+csharp_style_expression_bodied_indexers = false
+csharp_style_expression_bodied_accessors = false
+csharp_style_expression_bodied_lambdas = false
+csharp_style_expression_bodied_local_functions = false
+
+# Pattern matching preferences
+
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_switch_expression = false
+csharp_style_prefer_pattern_matching = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_extended_property_pattern = true
+
+# Code block preferences
+
+csharp_prefer_braces = true
+csharp_prefer_simple_using_statement = false
+
+# Using directive preferences
+
+csharp_using_directive_placement = outside_namespace
+
+# Namespace naming preferences
+
+dotnet_style_namespace_match_folder = true
+
+# Undocumented preferences
+
+csharp_style_prefer_method_group_conversion = false
+csharp_style_prefer_top_level_statements = false
+
+### Formatting rules ###
+
+## .NET formatting options ##
+
+# Using directive options
+
+dotnet_sort_system_directives_first = true
+dotnet_separate_import_directive_groups = true
+
+## C# formatting options ##
+
+# New-line options
+
+# TNIS-13005: Enabling this setting breaks Resharper indentation for lambdas
+#csharp_new_line_before_open_brace = accessors, anonymous_methods, anonymous_types, control_blocks, events, indexers, lambdas, local_functions, methods, object_collection_array_initializers, properties, types
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+# Enabling this setting breaks Resharper formatting for an enum field reference that is
+# deeply nested in an object initializer.
+# 
+# For an example, see TDataExchangeGeneralEnricher_CernInfrastructureObstruction.
+#csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation options
+
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_block_contents = true
+# TNIS-13005: Enabling this setting breaks Resharper indentation for lambdas
+#csharp_indent_braces = false
+# TNIS-13005: Enabling this setting breaks Resharper indentation for lambdas
+#csharp_indent_case_contents_when_block = true
+
+# Spacing options
+
+csharp_space_after_cast = true
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_after_comma = true
+csharp_space_before_comma = false
+csharp_space_after_dot = false
+csharp_space_before_dot = false
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_around_declaration_statements = false
+csharp_space_before_open_square_brackets = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_square_brackets = false
+
+# Wrap options
+
+csharp_preserve_single_line_statements = false
+csharp_preserve_single_line_blocks = true
+
+### Naming styles ###
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.private_fields_camel_case_begins_with_underscore.symbols = private_fields
+dotnet_naming_rule.private_fields_camel_case_begins_with_underscore.style = camel_case_begins_with_underscore
+dotnet_naming_rule.private_fields_camel_case_begins_with_underscore.severity = error
+
+dotnet_naming_rule.private_static_fields_camel_case_begins_with_underscore.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_camel_case_begins_with_underscore.style = camel_case_begins_with_underscore
+dotnet_naming_rule.private_static_fields_camel_case_begins_with_underscore.severity = error
+
+dotnet_naming_rule.private_static_readonly_fields_pascal_case.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_pascal_case.style = pascal_case
+dotnet_naming_rule.private_static_readonly_fields_pascal_case.severity = error
+
+dotnet_naming_rule.private_const_fields_pascal_case.symbols = private_const_fields
+dotnet_naming_rule.private_const_fields_pascal_case.style = pascal_case
+dotnet_naming_rule.private_const_fields_pascal_case.severity = error
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = *
+dotnet_naming_symbols.interface.required_modifiers = 
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = *
+dotnet_naming_symbols.types.required_modifiers = 
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = *
+dotnet_naming_symbols.non_field_members.required_modifiers = 
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private
+dotnet_naming_symbols.private_fields.required_modifiers = 
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static, readonly
+
+dotnet_naming_symbols.private_const_fields.applicable_kinds = field
+dotnet_naming_symbols.private_const_fields.applicable_accessibilities = private
+dotnet_naming_symbols.private_const_fields.required_modifiers = const
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix = 
+dotnet_naming_style.begins_with_i.word_separator = 
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix = 
+dotnet_naming_style.pascal_case.required_suffix = 
+dotnet_naming_style.pascal_case.word_separator = 
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.camel_case_begins_with_underscore.required_prefix = _
+dotnet_naming_style.camel_case_begins_with_underscore.required_suffix = 
+dotnet_naming_style.camel_case_begins_with_underscore.word_separator = 
+dotnet_naming_style.camel_case_begins_with_underscore.capitalization = camel_case
+
+#### .NET Compiler Platform general options ####
+
+# Change the default rule severity for all analyzer rules that are enabled by default
+dotnet_analyzer_diagnostic.severity = error
+
+#### .NET Compiler Platform code refactoring rules ####
+
+dotnet_style_operator_placement_when_wrapping = end_of_line
+
+#### ReSharper code style for C# ####
+
+## Blank Lines
+
+resharper_csharp_blank_lines_around_region = 1
+resharper_csharp_blank_lines_inside_region = 1
+resharper_csharp_blank_lines_before_single_line_comment = 1
+resharper_csharp_keep_blank_lines_in_declarations = 1
+resharper_csharp_remove_blank_lines_near_braces_in_declarations = true
+resharper_csharp_blank_lines_after_start_comment = 1
+resharper_csharp_blank_lines_between_using_groups = 1
+resharper_csharp_blank_lines_after_using_list = 1
+resharper_csharp_blank_lines_around_namespace = 1
+resharper_csharp_blank_lines_inside_namespace = 0
+resharper_csharp_blank_lines_after_file_scoped_namespace_directive = 1
+resharper_csharp_blank_lines_around_type = 1
+resharper_csharp_blank_lines_around_single_line_type = 1
+resharper_csharp_blank_lines_inside_type = 0
+resharper_csharp_blank_lines_around_field = 0
+resharper_csharp_blank_lines_around_single_line_field = 0
+resharper_csharp_blank_lines_around_property = 1
+resharper_csharp_blank_lines_around_single_line_property = 1
+resharper_csharp_blank_lines_around_auto_property = 1
+resharper_csharp_blank_lines_around_single_line_auto_property = 1
+resharper_csharp_blank_lines_around_accessor = 0
+resharper_csharp_blank_lines_around_single_line_accessor = 0
+resharper_csharp_blank_lines_around_invocable = 1
+resharper_csharp_blank_lines_around_single_line_invocable = 1
+resharper_csharp_keep_blank_lines_in_code = 1
+resharper_csharp_remove_blank_lines_near_braces_in_code = true
+resharper_csharp_blank_lines_around_local_method = 1
+resharper_csharp_blank_lines_around_single_line_local_method = 1
+resharper_csharp_blank_lines_before_control_transfer_statements = 0
+resharper_csharp_blank_lines_after_control_transfer_statements = 0
+resharper_csharp_blank_lines_before_block_statements = 0
+resharper_csharp_blank_lines_after_block_statements = 1
+resharper_csharp_blank_lines_before_multiline_statements = 0
+resharper_csharp_blank_lines_after_multiline_statements = 0
+resharper_csharp_blank_lines_around_block_case_section = 0
+resharper_csharp_blank_lines_around_multiline_case_section = 0
+resharper_csharp_blank_lines_before_case = 0
+resharper_csharp_blank_lines_after_case = 0
+
+## Braces Layout
+
+resharper_csharp_type_declaration_braces = next_line
+resharper_csharp_indent_inside_namespace = true
+resharper_csharp_invocable_declaration_braces = next_line
+resharper_csharp_anonymous_method_declaration_braces = next_line_shifted_2
+resharper_csharp_accessor_owner_declaration_braces = next_line
+resharper_csharp_accessor_declaration_braces = next_line
+resharper_csharp_case_block_braces = next_line_shifted_2
+resharper_csharp_initializer_braces = next_line_shifted_2
+resharper_csharp_use_continuous_indent_inside_initializer_braces = true
+resharper_csharp_other_braces = next_line
+resharper_csharp_allow_comment_after_lbrace = false
+resharper_csharp_empty_block_style = multiline
+
+## Syntax Style
+
+# 'var' usage in declarations
+
+resharper_csharp_for_built_in_types = use_var
+resharper_csharp_for_simple_types = use_var
+resharper_csharp_for_other_types = use_var
+
+# Instance members qualification
+
+resharper_csharp_instance_members_qualify_members = none
+resharper_csharp_instance_members_qualify_declared_in = base_class
+
+# Static members qualification
+
+resharper_csharp_static_members_qualify_with = declared_type
+resharper_csharp_static_members_qualify_members = none
+
+# Built-in types
+
+resharper_csharp_builtin_type_reference_style = use_keyword
+resharper_csharp_builtin_type_reference_for_member_access_style = use_keyword
+
+# Reference qualification and 'using' directives
+
+resharper_csharp_prefer_qualified_reference = false
+resharper_csharp_add_imports_to_deepest_scope = false
+resharper_csharp_qualified_using_at_nested_scope = false
+resharper_csharp_allow_alias = true
+resharper_csharp_can_use_global_alias = true
+
+# Modifiers
+
+resharper_csharp_default_private_modifier = explicit
+resharper_csharp_default_internal_modifier = explicit
+resharper_csharp_modifiers_order = public private protected internal file static extern new virtual abstract sealed override readonly unsafe required volatile async
+
+# Braces
+
+resharper_csharp_braces_for_ifelse = required
+resharper_csharp_braces_for_for = required
+resharper_csharp_braces_for_foreach = required
+resharper_csharp_braces_for_while = required
+resharper_csharp_braces_for_dowhile = required
+resharper_csharp_braces_for_using = required
+resharper_csharp_braces_for_lock = required
+resharper_csharp_braces_for_fixed = required
+resharper_csharp_braces_redundant = false
+
+# Code body
+
+resharper_csharp_method_or_operator_body = block_body
+resharper_csharp_local_function_body = block_body
+resharper_csharp_constructor_or_destructor_body = block_body
+resharper_csharp_accessor_owner_body = accessors_with_block_body
+resharper_csharp_namespace_body = block_scoped
+resharper_csharp_use_heuristics_for_body_style = false
+
+# Trailing comma
+
+resharper_csharp_trailing_comma_in_multiline_lists = false
+resharper_csharp_trailing_comma_in_singleline_lists = false
+
+# Object creation
+
+resharper_csharp_object_creation_when_type_evident = explicitly_typed
+resharper_csharp_object_creation_when_type_not_evident = explicitly_typed
+
+# Default value
+
+resharper_csharp_default_value_when_type_evident = default_literal
+resharper_csharp_default_value_when_type_not_evident = default_literal
+
+## Tabs, Indents, Alignment
+
+# Nested statements
+
+resharper_csharp_indent_nested_usings_stmt = false
+resharper_csharp_indent_nested_fixed_stmt = false
+resharper_csharp_indent_nested_lock_stmt = false
+resharper_csharp_indent_nested_for_stmt = true
+resharper_csharp_indent_nested_foreach_stmt =  true
+resharper_csharp_indent_nested_while_stmt = true
+
+# Parenthesis
+
+resharper_csharp_use_continuous_indent_inside_parens = true
+resharper_csharp_indent_method_decl_pars = outside_and_inside
+resharper_csharp_indent_invocation_pars = outside_and_inside
+resharper_csharp_indent_statement_pars = outside_and_inside
+resharper_csharp_indent_typeparam_angles = outside_and_inside
+resharper_csharp_indent_typearg_angles = outside_and_inside
+resharper_csharp_indent_pars = outside_and_inside
+
+# Preprocessor directives
+
+resharper_csharp_indent_preprocessor_if = no_indent
+resharper_csharp_indent_preprocessor_region = usual_indent
+resharper_csharp_indent_preprocessor_other = no_indent
+
+# Other indents
+
+resharper_indent_switch_labels = true
+resharper_csharp_outdent_statement_labels = true
+resharper_csharp_indent_type_constraints = true
+resharper_csharp_stick_comment = false
+resharper_csharp_place_comments_at_first_column = false
+resharper_csharp_use_indent_from_previous_element = true
+resharper_csharp_indent_braces_inside_statement_conditions = true
+
+# Align multiline constructs
+
+resharper_csharp_alignment_tab_fill_style = use_spaces
+resharper_csharp_allow_far_alignment = true
+resharper_csharp_align_multiline_parameter = true
+resharper_csharp_align_multiline_extends_list = true
+resharper_csharp_align_linq_query = true
+resharper_csharp_align_multiline_binary_expressions_chain = true
+resharper_csharp_outdent_binary_ops = false
+resharper_csharp_align_multiline_calls_chain = true
+resharper_csharp_outdent_dots = false
+resharper_csharp_align_multiline_array_and_object_initializer = false
+resharper_csharp_align_multiline_switch_expression = false
+resharper_csharp_align_multiline_property_pattern = false
+resharper_csharp_align_multiline_list_pattern = false
+resharper_csharp_align_multiline_binary_patterns = false
+resharper_csharp_outdent_binary_pattern_ops = false
+resharper_csharp_indent_anonymous_method_block = true
+resharper_csharp_align_first_arg_by_paren = false
+resharper_csharp_align_multiline_argument = true
+resharper_csharp_align_tuple_components = true
+resharper_csharp_align_multiline_expression = true
+resharper_csharp_align_multiline_statement_conditions = true
+resharper_csharp_align_multiline_for_stmt = true
+resharper_csharp_align_multiple_declaration = true
+resharper_csharp_align_multline_type_parameter_list = true
+resharper_csharp_align_multline_type_parameter_constrains = true
+resharper_csharp_outdent_commas = false
+
+## Line Breaks
+
+# General
+
+resharper_csharp_keep_user_linebreaks = true
+resharper_csharp_max_line_length = 140
+resharper_csharp_wrap_before_comma = false
+resharper_csharp_wrap_before_eq = false
+resharper_csharp_special_else_if_treatment = true
+resharper_csharp_insert_final_newline = true
+
+# Arrangement of attributes
+
+resharper_csharp_keep_existing_attribute_arrangement = false
+resharper_csharp_place_type_attribute_on_same_line = false
+resharper_csharp_place_method_attribute_on_same_line = false
+resharper_csharp_place_accessorholder_attribute_on_same_line = false
+resharper_csharp_place_accessor_attribute_on_same_line = false
+resharper_csharp_place_field_attribute_on_same_line = false
+resharper_csharp_place_record_field_attribute_on_same_line = true
+
+# Arrangement of method signatures
+
+resharper_csharp_place_constructor_initializer_on_same_line = false
+resharper_csharp_place_expr_method_on_single_line = true
+resharper_csharp_place_expr_property_on_single_line = true
+resharper_csharp_place_expr_accessor_on_single_line = true
+
+# Arrangement of type parameters, constraints, and base types
+
+resharper_csharp_place_type_constraints_on_same_line = false
+resharper_csharp_wrap_before_first_type_parameter_constraint = true
+
+# Arrangement of declaration blocks
+
+resharper_csharp_place_abstract_accessorholder_on_single_line = true
+
+# Arrangement of statements
+
+resharper_new_line_before_else = true
+resharper_new_line_before_while = true
+resharper_new_line_before_catch = true
+resharper_new_line_before_finally = true
+resharper_wrap_for_stmt_header_style = chop_if_long
+resharper_wrap_multiple_declaration_style = chop_always
+
+## Spaces
+
+# Preserve existing formatting
+
+resharper_csharp_extra_spaces = remove_all
+
+# Before parentheses in statements
+
+resharper_csharp_space_before_if_parentheses = true
+resharper_csharp_space_before_while_parentheses = true
+resharper_csharp_space_before_catch_parentheses = true
+resharper_csharp_space_before_switch_parentheses = true
+resharper_csharp_space_before_for_parentheses = true
+resharper_csharp_space_before_foreach_parentheses = true
+resharper_csharp_space_before_using_parentheses = true
+resharper_csharp_space_before_lock_parentheses = true
+resharper_csharp_space_before_fixed_parentheses = true
+
+# Before other parentheses
+
+resharper_csharp_space_before_method_call_parentheses = false
+resharper_csharp_space_before_empty_method_call_parentheses = false
+resharper_csharp_space_before_method_parentheses = false
+resharper_csharp_space_before_empty_method_parentheses = false
+resharper_csharp_space_before_typeof_parentheses = false
+resharper_csharp_space_before_default_parentheses = false
+resharper_csharp_space_before_checked_parentheses = false
+resharper_csharp_space_before_sizeof_parentheses = false
+resharper_csharp_space_before_nameof_parentheses = false
+resharper_csharp_space_before_new_parentheses = false
+resharper_csharp_space_between_keyword_and_expression = true
+resharper_csharp_space_between_keyword_and_type = false
+
+# Within parentheses in statements
+
+resharper_csharp_space_within_if_parentheses = false
+resharper_csharp_space_within_while_parentheses = false
+resharper_csharp_space_within_catch_parentheses = false
+resharper_csharp_space_within_switch_parentheses = false
+resharper_csharp_space_within_for_parentheses = false
+resharper_csharp_space_within_foreach_parentheses = false
+resharper_csharp_space_within_using_parentheses = false
+resharper_csharp_space_within_lock_parentheses = false
+resharper_csharp_space_within_fixed_parentheses = false
+
+# Within other parentheses
+
+resharper_csharp_space_within_parentheses = false
+resharper_csharp_space_between_typecast_parentheses = false
+resharper_csharp_space_between_method_declaration_parameter_list_parentheses = false
+resharper_csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+resharper_csharp_space_between_method_call_parameter_list_parentheses = false
+resharper_csharp_space_between_method_call_empty_parameter_list_parentheses = false
+resharper_csharp_space_within_typeof_parentheses = false
+resharper_csharp_space_within_default_parentheses = false
+resharper_csharp_space_within_checked_parentheses = false
+resharper_csharp_space_within_sizeof_parentheses = false
+resharper_csharp_space_within_nameof_parentheses = false
+resharper_csharp_space_within_new_parentheses = false
+
+# Around array brackets
+
+resharper_csharp_space_before_array_access_brackets = false
+resharper_csharp_space_before_open_square_brackets = false
+resharper_csharp_space_before_array_rank_brackets = false
+resharper_csharp_space_within_array_access_brackets = false
+resharper_csharp_space_between_square_brackets = false
+resharper_csharp_space_within_array_rank_brackets = false
+resharper_csharp_space_within_array_rank_empty_brackets = false
+resharper_csharp_space_between_empty_square_bracket = false
+
+# Around angle brackets
+
+resharper_csharp_space_before_type_parameter_angle = false
+resharper_csharp_space_before_type_argument_angle = false
+resharper_csharp_space_within_type_parameter_angles = false
+resharper_csharp_space_within_type_argument_angles = false
+
+### ReSharper code style for XMLDOC ###
+
+## Tabs and indents
+
+resharper_xmldoc_indent_style = space
+# ReSharper currently ignores this setting. See https://youtrack.jetbrains.com/issue/RSRP-465678/XMLDOC-indent-settings-ignored.
+resharper_xmldoc_indent_size = 2
+resharper_xmldoc_tab_width = 2
+resharper_xmldoc_alignment_tab_fill_style = use_spaces
+resharper_xmldoc_allow_far_alignment = true
+
+## Line wrapping
+
+resharper_xmldoc_max_line_length = 140
+resharper_xmldoc_wrap_tags_and_pi = false
+
+## Processing instructions
+
+resharper_xmldoc_spaces_around_eq_in_pi_attribute = false
+resharper_xmldoc_space_after_last_pi_attribute = false
+resharper_xmldoc_pi_attribute_style = on_single_line
+resharper_xmldoc_pi_attributes_indent = align_by_first_attribute
+resharper_xmldoc_blank_line_after_pi = false
+
+## Inside of tag header
+
+resharper_xmldoc_spaces_around_eq_in_attribute = false
+resharper_xmldoc_space_after_last_attribute = false
+resharper_xmldoc_space_before_self_closing = false
+resharper_xmldoc_attribute_style = do_not_touch
+resharper_xmldoc_attribute_indent = align_by_first_attribute
+
+## Tag content
+
+resharper_xmldoc_keep_user_linebreaks = true
+resharper_xmldoc_linebreaks_inside_tags_for_multiline_elements = true
+resharper_xmldoc_linebreaks_inside_tags_for_elements_with_child_elements = false
+resharper_xmldoc_spaces_inside_tags = false
+resharper_xmldoc_wrap_text = false
+resharper_xmldoc_wrap_around_elements = false
+# ReSharper currently ignores the 'resharper_xmldoc_indent_size' setting. Once https://youtrack.jetbrains.com/issue/RSRP-465678/XMLDOC-indent-settings-ignored
+# is fixed, we should change the value of this setting to 'one_indent'.
+resharper_xmldoc_indent_child_elements = zero_indent
+resharper_xmldoc_indent_text = zero_indent
+
+## Around tags
+
+resharper_xmldoc_max_blank_lines_between_tags = 1
+resharper_xmldoc_linebreak_before_multiline_elements = true
+resharper_xmldoc_linebreak_before_singleline_elements = false
+
+[*.{xml,xsd,csproj,targets,proj,props,runsettings,config}]
+
+#### ReSharper code style for XML ####
+
+## Tabs and indents
+
+resharper_xml_indent_style = space
+resharper_xml_indent_size = 4
+resharper_xml_tab_width = 4
+resharper_xml_alignment_tab_fill_style = use_spaces
+resharper_xml_allow_far_alignment = true
+
+## Line wrapping
+
+resharper_xml_wrap_tags_and_pi = false
+
+## Processing instructions
+
+resharper_xml_spaces_around_eq_in_pi_attribute = false
+resharper_xml_space_after_last_pi_attribute = false
+resharper_xml_pi_attribute_style = on_single_line
+resharper_xml_pi_attributes_indent = align_by_first_attribute
+resharper_xml_blank_line_after_pi = false
+
+## Inside of tag header
+
+resharper_xml_spaces_around_eq_in_attribute = false
+resharper_xml_space_after_last_attribute = false
+resharper_xml_space_before_self_closing = true
+resharper_xml_attribute_style = do_not_touch
+resharper_xml_attribute_indent = align_by_first_attribute
+
+## Tag content
+
+resharper_xml_keep_user_linebreaks = true
+resharper_xml_linebreaks_inside_tags_for_multiline_elements = false
+resharper_xml_linebreaks_inside_tags_for_elements_with_child_elements = false
+resharper_xml_linebreaks_inside_tags_for_elements_longer_than = false
+resharper_xml_spaces_inside_tags = false
+resharper_xml_wrap_text = false
+resharper_xml_wrap_around_elements = false
+resharper_xml_indent_child_elements = one_indent
+resharper_xml_indent_text = zero_indent
+resharper_xml_max_blank_lines_between_tags = 1
+resharper_xml_linebreak_before_multiline_elements = false
+resharper_xml_linebreak_before_singleline_elements = false
+
+## Other
+
+resharper_xml_insert_final_newline = true

+ 3 - 0
.gitignore

@@ -22,3 +22,6 @@ project.lock.json
 
 
 # Build outputs
 # Build outputs
 build/target/
 build/target/
+
+# Benchmark results
+BenchmarkDotNet.Artifacts/

+ 1 - 0
CODEOWNERS

@@ -0,0 +1 @@
+*   @drieseng @WojciechNagorski

+ 44 - 0
Directory.Build.props

@@ -0,0 +1,44 @@
+<Project>
+    <Import Project="$(MSBuildThisFileFullPath).user" Condition="Exists('$(MSBuildThisFileFullPath).user')" />
+    
+    <!--
+        Assembly Info properties that apply to all projects/assemblies.
+    -->
+    <PropertyGroup>
+        <SignAssembly>true</SignAssembly>
+        <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)src\Renci.SshNet.snk</AssemblyOriginatorKeyFile>
+        <GenerateDocumentationFile>true</GenerateDocumentationFile>
+        <LangVersion>latest</LangVersion>
+        <WarningLevel>9999</WarningLevel>
+        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+        <AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
+    </PropertyGroup>
+
+    <!--
+        Code analysis properties.
+    -->
+    <PropertyGroup>
+        <EnableNETAnalyzers>false</EnableNETAnalyzers>
+        <AnalysisLevel>preview-All</AnalysisLevel>
+        <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
+    </PropertyGroup>
+
+    <!--
+        Add the stylecop config to each project.
+    -->
+    <ItemGroup>
+        <AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
+    </ItemGroup>
+
+    <!--
+        Use fixed version of analyzers.
+    -->
+    <ItemGroup>
+        <!--
+        <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0-preview1.23165.1" PrivateAssets="all" />
+        <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
+        <PackageReference Include="Meziantou.Analyzer" Version="2.0.54" PrivateAssets="all" />
+        <PackageReference Include="SonarAnalyzer.CSharp" Version="8.55.0.65544" PrivateAssets="all" />
+        -->        
+    </ItemGroup>
+</Project>

+ 2 - 0
LICENSE

@@ -1,5 +1,7 @@
 The MIT License (MIT)
 The MIT License (MIT)
 
 
+Copyright (c) Renci, Oleg Kapeljushnik, Gert Driesen and contributors
+
 Permission is hereby granted, free of charge, to any person obtaining a copy
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 in the Software without restriction, including without limitation the rights

+ 8 - 39
README.md

@@ -80,7 +80,7 @@ the missing test once you figure things out.  🤓
 * RSA in OpenSSL PEM and ssh.com format
 * RSA in OpenSSL PEM and ssh.com format
 * DSA in OpenSSL PEM and ssh.com format
 * DSA in OpenSSL PEM and ssh.com format
 * ECDSA 256/384/521 in OpenSSL PEM format
 * ECDSA 256/384/521 in OpenSSL PEM format
-* ED25519 in OpenSSH key format
+* ECDSA 256/384/521, ED25519 and RSA in OpenSSH key format
 
 
 Private keys can be encrypted using one of the following cipher methods:
 Private keys can be encrypted using one of the following cipher methods:
 * DES-EDE3-CBC
 * DES-EDE3-CBC
@@ -97,6 +97,8 @@ Private keys can be encrypted using one of the following cipher methods:
 * ecdsa-sha2-nistp256
 * ecdsa-sha2-nistp256
 * ecdsa-sha2-nistp384
 * ecdsa-sha2-nistp384
 * ecdsa-sha2-nistp521
 * ecdsa-sha2-nistp521
+* rsa-sha2-512
+* rsa-sha2-256
 * ssh-rsa
 * ssh-rsa
 * ssh-dss
 * ssh-dss
 
 
@@ -116,15 +118,9 @@ Private keys can be encrypted using one of the following cipher methods:
 
 
 ## Framework Support
 ## Framework Support
 **SSH.NET** supports the following target frameworks:
 **SSH.NET** supports the following target frameworks:
-* .NET Framework 3.5
-* .NET Framework 4.0 (and higher)
-* .NET Standard 1.3
-* .NET Standard 2.0
-* Silverlight 4
-* Silverlight 5
-* Windows Phone 7.1
-* Windows Phone 8.0
-* Universal Windows Platform 10
+* .NETFramework 4.6.2 (and higher)
+* .NET Standard 2.0 and 2.1
+* .NET 6 (and higher)
 
 
 ## Usage
 ## Usage
 
 
@@ -149,45 +145,18 @@ using (var client = new SftpClient(connectionInfo))
 Establish a SSH connection using user name and password, and reject the connection if the fingerprint of the server does not match the expected fingerprint:
 Establish a SSH connection using user name and password, and reject the connection if the fingerprint of the server does not match the expected fingerprint:
 
 
 ```cs
 ```cs
-byte[] expectedFingerPrint = new byte[] {
-                                            0x66, 0x31, 0xaf, 0x00, 0x54, 0xb9, 0x87, 0x31,
-                                            0xff, 0x58, 0x1c, 0x31, 0xb1, 0xa2, 0x4c, 0x6b
-                                        };
+string expectedFingerPrint = "LKOy5LvmtEe17S4lyxVXqvs7uPMy+yF79MQpHeCs/Qo";
 
 
 using (var client = new SshClient("sftp.foo.com", "guest", "pwd"))
 using (var client = new SshClient("sftp.foo.com", "guest", "pwd"))
 {
 {
     client.HostKeyReceived += (sender, e) =>
     client.HostKeyReceived += (sender, e) =>
         {
         {
-            if (expectedFingerPrint.Length == e.FingerPrint.Length)
-            {
-                for (var i = 0; i < expectedFingerPrint.Length; i++)
-                {
-                    if (expectedFingerPrint[i] != e.FingerPrint[i])
-                    {
-                        e.CanTrust = false;
-                        break;
-                    }
-                }
-            }
-            else
-            {
-                e.CanTrust = false;
-            }
+            e.CanTrust = expectedFingerPrint.Equals(e.FingerPrintSHA256);
         };
         };
     client.Connect();
     client.Connect();
 }
 }
 ```
 ```
 
 
-## Building SSH.NET
-
-Software                          | net35 | net40 | netstandard1.3 | netstandard2.0 | sl4 | sl5 | wp71 | wp8 | uap10.0 |
---------------------------------- | :---: | :---: | :------------: | :------------: | :-: | :-: | :--: | :-: | :-----: |
-Windows Phone SDK 8.0             |       |       |                |                | x   | x   | x    | x   |
-Visual Studio 2012 Update 5       | x     | x     |                |                | x   | x   | x    | x   |
-Visual Studio 2015 Update 3       | x     | x     |                |                |     | x   |      | x   | x
-Visual Studio 2017                | x     | x     | x              | x              |     |     |      |     | 
-Visual Studio 2019                | x     | x     | x              | x              |     |     |      |     | 
-
 ## Supporting SSH.NET
 ## Supporting SSH.NET
 
 
 Do you or your company rely on **SSH.NET** in your projects? If you want to encourage us to keep on going and show us that you appreciate our work, please consider becoming a [sponsor](https://github.com/sponsors/sshnet) through GitHub Sponsors.
 Do you or your company rely on **SSH.NET** in your projects? If you want to encourage us to keep on going and show us that you appreciate our work, please consider becoming a [sponsor](https://github.com/sponsors/sshnet) through GitHub Sponsors.

+ 6 - 6
appveyor.yml

@@ -1,14 +1,14 @@
-os: Visual Studio 2019
+os: Visual Studio 2022
 
 
 before_build:
 before_build:
-  - nuget restore src\Renci.SshNet.VS2019.sln
+  - nuget restore src\Renci.SshNet.sln
 
 
 build:
 build:
-  project: src\Renci.SshNet.VS2019.sln
+  project: src\Renci.SshNet.sln
   verbosity: minimal
   verbosity: minimal
 
 
 test_script:
 test_script:
 - cmd: >-
 - 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\net462\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration" --blame
+    
+    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net7.0\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration" --blame

+ 22 - 97
build/build.proj

@@ -8,86 +8,41 @@
 		<MSBuildTasksPackageId>MSBuildTasks</MSBuildTasksPackageId>
 		<MSBuildTasksPackageId>MSBuildTasks</MSBuildTasksPackageId>
 		<MSBuildTasksPackageVersion>1.5.0.214</MSBuildTasksPackageVersion>
 		<MSBuildTasksPackageVersion>1.5.0.214</MSBuildTasksPackageVersion>
 	</PropertyGroup>
 	</PropertyGroup>
-	
-	<ItemGroup>
-		<VisualStudioVersionClassic Include="2012">
-			<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.VS2012.sln</SolutionFile>
-			<ToolsVersion>14.0</ToolsVersion>
-			<VisualStudioVersion>14.0</VisualStudioVersion>
-		</VisualStudioVersionClassic>
-		<VisualStudioVersionClassic Include="2015">
-			<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.VS2015.sln</SolutionFile>
-			<ToolsVersion>14.0</ToolsVersion>
-			<VisualStudioVersion>14.0</VisualStudioVersion>
-		</VisualStudioVersionClassic>
-	</ItemGroup>
-	
-	<ItemGroup>
-		<VisualStudioVersionModern Include="2019">
-			<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.VS2019.sln</SolutionFile>
-			<VisualStudioVersion>16.0</VisualStudioVersion>
-		</VisualStudioVersionModern>
-	</ItemGroup>
 
 
 	<ItemGroup>
 	<ItemGroup>
-		<TargetFrameworkClassic Include="Windows Phone Silverlight 7.1">
-			<OutputDirectory>Renci.SshNet.WindowsPhone\bin\$(Configuration)</OutputDirectory>
-			<Moniker>wp71</Moniker>
-		</TargetFrameworkClassic>
-		<TargetFrameworkClassic Include="Windows Phone Silverlight 8.0">
-			<OutputDirectory>Renci.SshNet.WindowsPhone8\bin\$(Configuration)</OutputDirectory>
-			<Moniker>wp8</Moniker>
-		</TargetFrameworkClassic>
-		<TargetFrameworkClassic Include="Silverlight 4">
-			<OutputDirectory>Renci.SshNet.Silverlight\bin\$(Configuration)</OutputDirectory>
-			<Moniker>sl4</Moniker>
-		</TargetFrameworkClassic>
-		<TargetFrameworkClassic Include="Silverlight 5">
-			<OutputDirectory>Renci.SshNet.Silverlight5\bin\$(Configuration)</OutputDirectory>
-			<Moniker>sl5</Moniker>
-		</TargetFrameworkClassic>
-		<TargetFrameworkClassic Include="Universal Windows Platform 10">
-			<OutputDirectory>Renci.SshNet.UAP10\bin\$(Configuration)</OutputDirectory>
-			<Moniker>uap10</Moniker>
-		</TargetFrameworkClassic>
+		<VisualStudioVersionModern Include="2022">
+			<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.sln</SolutionFile>
+			<VisualStudioVersion>17.0</VisualStudioVersion>
+		</VisualStudioVersionModern>
 	</ItemGroup>
 	</ItemGroup>
 
 
 	<ItemGroup>
 	<ItemGroup>
-		<TargetFrameworkModern Include=".NET Framework 3.5">
-			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net35</OutputDirectory>
-			<Moniker>net35</Moniker>
-		</TargetFrameworkModern>
-		<TargetFrameworkModern Include=".NET Framework 4.0">
-			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net40</OutputDirectory>
-			<Moniker>net40</Moniker>
-		</TargetFrameworkModern>
-		<TargetFrameworkModern Include=".NETStandard 1.3">
-			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard1.3</OutputDirectory>
-			<Moniker>netstandard1.3</Moniker>
+		<TargetFrameworkModern Include=".NET Framework 4.6.2">
+			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net462</OutputDirectory>
+			<Moniker>net462</Moniker>
 		</TargetFrameworkModern>
 		</TargetFrameworkModern>
 		<TargetFrameworkModern Include=".NETStandard 2.0">
 		<TargetFrameworkModern Include=".NETStandard 2.0">
 			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard2.0</OutputDirectory>
 			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard2.0</OutputDirectory>
 			<Moniker>netstandard2.0</Moniker>
 			<Moniker>netstandard2.0</Moniker>
 		</TargetFrameworkModern>
 		</TargetFrameworkModern>
+		<TargetFrameworkModern Include=".NETStandard 2.1">
+			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard2.1</OutputDirectory>
+			<Moniker>netstandard2.1</Moniker>
+		</TargetFrameworkModern>
+		<TargetFrameworkModern Include=".NET 6.0">
+			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net6.0</OutputDirectory>
+			<Moniker>net6.0</Moniker>
+		</TargetFrameworkModern>
+		<TargetFrameworkModern Include=".NET 7.0">
+			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net7.0</OutputDirectory>
+			<Moniker>net7.0</Moniker>
+		</TargetFrameworkModern>
 	</ItemGroup>
 	</ItemGroup>
-	
-	<Target Name="CleanClassic" DependsOnTargets="CleanSolutionClassic">
-		<RemoveDir Directories="$(MSBuildThisFileDirectory)target"/>
-	</Target>
-	
+
 	<Target Name="CleanModern" DependsOnTargets="CleanSolutionModern">
 	<Target Name="CleanModern" DependsOnTargets="CleanSolutionModern">
 		<RemoveDir Directories="$(MSBuildThisFileDirectory)target"/>
 		<RemoveDir Directories="$(MSBuildThisFileDirectory)target"/>
 	</Target>
 	</Target>
 
 
-	<Target Name="CleanSolutionClassic" Outputs="%(VisualStudioVersionClassic.Identity)">
-		<ItemGroup>
-			<ProjectToBuild Remove="@(ProjectToBuild)"/>
-			<ProjectToBuild Include="%(VisualStudioVersionClassic.SolutionFile)">
-				<Properties>Configuration=Release;VisualStudioVersion=%(VisualStudioVersionClassic.VisualStudioVersion)</Properties>
-			</ProjectToBuild>
-		</ItemGroup>
-		<MSBuild Projects="@(ProjectToBuild)" ToolsVersion="%(VisualStudioVersionClassic.ToolsVersion)" Targets="Clean"/>
-	</Target>
 	
 	
 	<Target Name="CleanSolutionModern" Outputs="%(VisualStudioVersionModern.Identity)">
 	<Target Name="CleanSolutionModern" Outputs="%(VisualStudioVersionModern.Identity)">
 		<ItemGroup>
 		<ItemGroup>
@@ -99,26 +54,11 @@
 		<MSBuild Projects="@(ProjectToBuild)" Targets="Clean"/>
 		<MSBuild Projects="@(ProjectToBuild)" Targets="Clean"/>
 	</Target>
 	</Target>
 
 
-	<Target Name="RestoreNuGetPackagesClassic" DependsOnTargets="DownloadNuGet" Outputs="%(VisualStudioVersionClassic.Identity)">
-		<Message Text="Restoring nuget packages for '%(VisualStudioVersionClassic.SolutionFile)'..." Importance="High"/>
-		<Exec Command="$(NuGetExe) restore &quot;%(VisualStudioVersionClassic.SolutionFile)&quot;"/>
-	</Target>
-	
 	<Target Name="RestoreNuGetPackagesModern" DependsOnTargets="DownloadNuGet" Outputs="%(VisualStudioVersionModern.Identity)">
 	<Target Name="RestoreNuGetPackagesModern" DependsOnTargets="DownloadNuGet" Outputs="%(VisualStudioVersionModern.Identity)">
 		<Message Text="Restoring nuget packages for '%(VisualStudioVersionModern.SolutionFile)'..." Importance="High"/>
 		<Message Text="Restoring nuget packages for '%(VisualStudioVersionModern.SolutionFile)'..." Importance="High"/>
 		<Exec Command="$(NuGetExe) restore &quot;%(VisualStudioVersionModern.SolutionFile)&quot;"/>
 		<Exec Command="$(NuGetExe) restore &quot;%(VisualStudioVersionModern.SolutionFile)&quot;"/>
 	</Target>
 	</Target>
-	
-	<Target Name="BuildClassic" DependsOnTargets="RestoreNuGetPackagesClassic" Outputs="%(VisualStudioVersionClassic.Identity)">
-		<ItemGroup>
-			<ProjectToBuild Remove="@(ProjectToBuild)"/>
-			<ProjectToBuild Include="%(VisualStudioVersionClassic.SolutionFile)">
-				<Properties>Configuration=Release;VisualStudioVersion=%(VisualStudioVersionClassic.VisualStudioVersion)</Properties>
-			</ProjectToBuild>
-		</ItemGroup>
-		<MSBuild Projects="@(ProjectToBuild)" ToolsVersion="%(VisualStudioVersionClassic.ToolsVersion)" Targets="Rebuild"/>
-	</Target>
-	
+
 	<Target Name="BuildModern" DependsOnTargets="RestoreNuGetPackagesModern" Outputs="%(VisualStudioVersionModern.Identity)">
 	<Target Name="BuildModern" DependsOnTargets="RestoreNuGetPackagesModern" Outputs="%(VisualStudioVersionModern.Identity)">
 		<ItemGroup>
 		<ItemGroup>
 			<ProjectToBuild Remove="@(ProjectToBuild)"/>
 			<ProjectToBuild Remove="@(ProjectToBuild)"/>
@@ -131,12 +71,7 @@
 	
 	
 	<Target Name="Package" DependsOnTargets="CreateNuGetPackage;CreateBinPackage;GenerateHelpFile"/>
 	<Target Name="Package" DependsOnTargets="CreateNuGetPackage;CreateBinPackage;GenerateHelpFile"/>
 	
 	
-	<Target Name="ValidatePackage" DependsOnTargets="ValidatePackageClassic;ValidatePackageModern"/>
-	
-	<Target Name="ValidatePackageClassic" DependsOnTargets="CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkClassic.Identity)">
-		<Error Text="The 'Renci.SshNet.dll' file is not available for %(TargetFrameworkClassic.Identity) in $(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)." Condition="!Exists('$(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)\Renci.SshNet.dll')"/>
-		<Error Text="The 'Renci.SshNet.xml' file is not available for %(TargetFrameworkClassic.Identity) in $(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)." Condition="!Exists('$(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)\Renci.SshNet.xml')"/>
-	</Target>
+	<Target Name="ValidatePackage" DependsOnTargets="ValidatePackageModern"/>
 
 
 	<Target Name="ValidatePackageModern" DependsOnTargets="CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkModern.Identity)">
 	<Target Name="ValidatePackageModern" DependsOnTargets="CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkModern.Identity)">
 		<Error Text="The 'Renci.SshNet.dll' file is not available for %(TargetFrameworkModern.Identity) in $(NuGetPackageDirectory)\lib\%(TargetFrameworkModern.Moniker)." Condition="!Exists('$(NuGetPackageDirectory)\lib\%(TargetFrameworkModern.Moniker)\Renci.SshNet.dll')"/>
 		<Error Text="The 'Renci.SshNet.dll' file is not available for %(TargetFrameworkModern.Identity) in $(NuGetPackageDirectory)\lib\%(TargetFrameworkModern.Moniker)." Condition="!Exists('$(NuGetPackageDirectory)\lib\%(TargetFrameworkModern.Moniker)\Renci.SshNet.dll')"/>
@@ -153,16 +88,6 @@
 		<Move SourceFiles="$(MSBuildThisFileDirectory)target\help\SshNet.Help.chm" DestinationFiles="$(MSBuildThisFileDirectory)target\SSH.NET-$(ReleaseVersion)-help.chm"/>
 		<Move SourceFiles="$(MSBuildThisFileDirectory)target\help\SshNet.Help.chm" DestinationFiles="$(MSBuildThisFileDirectory)target\SSH.NET-$(ReleaseVersion)-help.chm"/>
 	</Target>
 	</Target>
 
 
-	<Target Name="PreparePackageClassic" DependsOnTargets="BuildClassic;CheckNuGetPackageDirectory;CheckBinaryZipPackageDirectory" Outputs="%(TargetFrameworkClassic.Identity)">
-		<ItemGroup>
-			<BuildOutput Remove="@(BuildOutput)"/>
-			<BuildOutput Include="$(MSBuildThisFileDirectory)..\src\%(TargetFrameworkClassic.OutputDirectory)\Renci.SshNet.dll"/>
-			<BuildOutput Include="$(MSBuildThisFileDirectory)..\src\%(TargetFrameworkClassic.OutputDirectory)\Renci.SshNet.xml"/>
-		</ItemGroup>
-		<Copy SourceFiles="@(BuildOutput)" DestinationFolder="$(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)"/>
-		<Copy SourceFiles="@(BuildOutput)" DestinationFolder="$(BinPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)"/>
-	</Target>
-	
 	<Target Name="PreparePackageModern" DependsOnTargets="BuildModern;CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkModern.Identity)">
 	<Target Name="PreparePackageModern" DependsOnTargets="BuildModern;CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkModern.Identity)">
 		<ItemGroup>
 		<ItemGroup>
 			<BuildOutput Remove="@(BuildOutput)"/>
 			<BuildOutput Remove="@(BuildOutput)"/>

+ 6 - 25
build/nuget/SSH.NET.nuspec

@@ -6,7 +6,7 @@
         <title>SSH.NET</title>
         <title>SSH.NET</title>
         <authors>Renci</authors>
         <authors>Renci</authors>
         <owners>olegkap,drieseng</owners>
         <owners>olegkap,drieseng</owners>
-        <licenseUrl>https://github.com/sshnet/SSH.NET/blob/master/LICENSE</licenseUrl>
+        <license type="expression">MIT</license>
         <projectUrl>https://github.com/sshnet/SSH.NET/</projectUrl>
         <projectUrl>https://github.com/sshnet/SSH.NET/</projectUrl>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
         <description>SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism and with broad framework support.</description>
         <description>SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism and with broad framework support.</description>
@@ -16,37 +16,18 @@
         <language>en-US</language>
         <language>en-US</language>
         <tags>ssh scp sftp</tags>
         <tags>ssh scp sftp</tags>
         <dependencies>
         <dependencies>
-          <group targetFramework="net35" />
-          <group targetFramework="net40" />
-          <group targetFramework="netstandard1.3">
-            <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
-            <dependency id="System.Diagnostics.TraceSource" version="4.3.0" />
-            <dependency id="System.Net.NameResolution" version="4.3.0" />
-            <dependency id="System.Net.Sockets" version="4.3.0" />
-            <dependency id="System.Threading.Thread" version="4.3.0" />
-            <dependency id="System.Threading.ThreadPool" version="4.3.0" />
-            <dependency id="System.Threading.Timer" version="4.3.0" />
-            <dependency id="System.Xml.XmlDocument" version="4.3.0" />
-            <dependency id="System.Xml.XPath.XmlDocument" version="4.3.0" />
-          </group>
+          <group targetFramework="net462" />
           <group targetFramework="netstandard2.0">
           <group targetFramework="netstandard2.0">
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
-          </group>
-          <group targetFramework="sl4">
-            <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
-          </group>
-          <group targetFramework="sl5">
-            <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
-          </group>
-          <group targetFramework="wp71">
+          </group>          
+          <group targetFramework="netstandard2.1">
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
           </group>
           </group>
-          <group targetFramework="wp8">
+          <group targetFramework="net6.0">
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
           </group>
           </group>
-          <group targetFramework="uap10.0">
+          <group targetFramework="net7.0">
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
-            <dependency id="System.Xml.XPath.XmlDocument" version="4.3.0" />
           </group>
           </group>
         </dependencies>
         </dependencies>
     </metadata>
     </metadata>

+ 14 - 23
build/sandcastle/SSH.NET.shfbproj

@@ -1,21 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
   <PropertyGroup>
   <PropertyGroup>
-    <!--
-      The configuration and platform will be used to determine which assemblies to include from solution and
-      project documentation sources
-    -->
+    <!-- A target framework version is required by Visual Studio. It can be any version with a targeting pack installed. -->
+    <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
+    <!-- The configuration and platform will be used to determine which assemblies to include from solution and
+         project documentation sources -->
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+
     <SchemaVersion>2.0</SchemaVersion>
     <SchemaVersion>2.0</SchemaVersion>
     <ProjectGuid>{f7266fb1-f50a-4a5b-b35a-5ea8ebdc1be9}</ProjectGuid>
     <ProjectGuid>{f7266fb1-f50a-4a5b-b35a-5ea8ebdc1be9}</ProjectGuid>
-    <SHFBSchemaVersion>2015.6.5.0</SHFBSchemaVersion>
+    <SHFBSchemaVersion>2017.9.26.0</SHFBSchemaVersion>
     <!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual Studio adds them anyway -->
     <!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual Studio adds them anyway -->
     <AssemblyName>Documentation</AssemblyName>
     <AssemblyName>Documentation</AssemblyName>
     <RootNamespace>Documentation</RootNamespace>
     <RootNamespace>Documentation</RootNamespace>
     <Name>Documentation</Name>
     <Name>Documentation</Name>
     <!-- SHFB properties -->
     <!-- SHFB properties -->
-    <FrameworkVersion>.NET Framework 4.0</FrameworkVersion>
+    <FrameworkVersion>.NET Framework 4.6.2</FrameworkVersion>
     <OutputPath>..\target\help</OutputPath>
     <OutputPath>..\target\help</OutputPath>
     <HtmlHelpName>SshNet.Help</HtmlHelpName>
     <HtmlHelpName>SshNet.Help</HtmlHelpName>
     <Language>en-US</Language>
     <Language>en-US</Language>
@@ -24,25 +25,15 @@
     <SyntaxFilters>C#</SyntaxFilters>
     <SyntaxFilters>C#</SyntaxFilters>
     <SdkLinkTarget>Blank</SdkLinkTarget>
     <SdkLinkTarget>Blank</SdkLinkTarget>
     <RootNamespaceContainer>False</RootNamespaceContainer>
     <RootNamespaceContainer>False</RootNamespaceContainer>
-    <PresentationStyle>VS2010</PresentationStyle>
+    <PresentationStyle>VS2013</PresentationStyle>
     <Preliminary>False</Preliminary>
     <Preliminary>False</Preliminary>
     <NamingMethod>Guid</NamingMethod>
     <NamingMethod>Guid</NamingMethod>
-    <HelpTitle>SSH.NET Client Library Documenation</HelpTitle>
+    <HelpTitle>SSH.NET Client Library Documentation</HelpTitle>
     <ContentPlacement>AboveNamespaces</ContentPlacement>
     <ContentPlacement>AboveNamespaces</ContentPlacement>
-    <ComponentConfigurations>
-      <ComponentConfig id="Code Block Component" enabled="True">
-        <component id="Code Block Component">
-  <basePath value="{@HtmlEncProjectFolder}" />
-  <outputPaths>{@HelpFormatOutputPaths}</outputPaths>
-  <allowMissingSource value="false" />
-  <removeRegionMarkers value="false" />
-  <colorizer syntaxFile="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.xml" styleFile="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.xsl" stylesheet="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.css" scriptFile="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.js" disabled="{@DisableCodeBlockComponent}" language="cs" tabSize="0" numberLines="false" outlining="false" keepSeeTags="false" defaultTitle="true" />
-</component>
-      </ComponentConfig>
-    </ComponentConfigurations>
+
     <DocumentationSources>
     <DocumentationSources>
-      <DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net40\Renci.SshNet.dll" xmlns="" />
-      <DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net40\Renci.SshNet.xml" xmlns="" />
+      <DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net462\Renci.SshNet.dll" xmlns="" />
+      <DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net462\Renci.SshNet.xml" xmlns="" />
     </DocumentationSources>
     </DocumentationSources>
     <MissingTags>Summary, Parameter, Returns, AutoDocumentCtors, TypeParameter, AutoDocumentDispose</MissingTags>
     <MissingTags>Summary, Parameter, Returns, AutoDocumentCtors, TypeParameter, AutoDocumentDispose</MissingTags>
     <BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
     <BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
@@ -54,7 +45,7 @@
     <CleanIntermediates>True</CleanIntermediates>
     <CleanIntermediates>True</CleanIntermediates>
   </PropertyGroup>
   </PropertyGroup>
   <!-- There are no properties for these groups.  AnyCPU needs to appear in order for Visual Studio to perform
   <!-- There are no properties for these groups.  AnyCPU needs to appear in order for Visual Studio to perform
-			 the build.  The others are optional common platform types that may appear. -->
+             the build.  The others are optional common platform types that may appear. -->
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@@ -73,4 +64,4 @@
   </PropertyGroup>
   </PropertyGroup>
   <!-- Import the SHFB build targets -->
   <!-- Import the SHFB build targets -->
   <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
   <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
-</Project>
+</Project>

二进制
images/logo/png/SS-NET-1280x640.png


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.ECDSA.Encrypted.txt → src/Data/Key.ECDSA.Encrypted.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.ECDSA.txt → src/Data/Key.ECDSA.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.ECDSA384.Encrypted.txt → src/Data/Key.ECDSA384.Encrypted.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.ECDSA384.txt → src/Data/Key.ECDSA384.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.ECDSA521.Encrypted.txt → src/Data/Key.ECDSA521.Encrypted.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.ECDSA521.txt → src/Data/Key.ECDSA521.txt


+ 9 - 0
src/Data/Key.OPENSSH.ECDSA.Encrypted.txt

@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCPSPglZ3
+w/7DmCJxYohONLAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz
+dHAyNTYAAABBBK6YY2NwwLDSMnJTD+a4OfitCDuG/MnY/AstPgh54xMrZF6Qr0U1H6kRMK
+Y6JJsj31CI97qDYrnTA00Sx5Jy6ywAAACwq4qisorVCP6yvrmf/fcPacX4+FVEmrHNn3fW
+TiYsat7oKoItqTiDaHkIloSX93ue3fzcKXpGPR/qnpu4SezkhL9Uk6ntiwO4coB/kbEnjk
+IFY6ZK0HENRXkdIuDG9qmoB0wjVPJ6L9e5RWZwiCPvNI2O60bpKOUs+tUSah1W7eTWy5Ss
+ttdTgmwqS84c5+uitK1DJh2jsDqfdGm7h1XpDJsRmIEXxTVu/EdtD0hZ/x4=
+-----END OPENSSH PRIVATE KEY-----

+ 9 - 0
src/Data/Key.OPENSSH.ECDSA.txt

@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSP3ZTb37LFvSmKweu03A5s/cwcw3+3
+jL1LqQK6D929xY1J2J6S91LXOBpBfz4l+8Ng7sWhu9P/hF/wmB2QRygrAAAAqMq583bKuf
+N2AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/dlNvfssW9KYrB
+67TcDmz9zBzDf7eMvUupAroP3b3FjUnYnpL3Utc4GkF/PiX7w2DuxaG70/+EX/CYHZBHKC
+sAAAAhALVqID3K/N7IazKNbhrg09r7rLLtjy81RLV+VDxloQnxAAAAC3NyaW5rZXNATkVP
+AQIDBA==
+-----END OPENSSH PRIVATE KEY-----

+ 11 - 0
src/Data/Key.OPENSSH.ECDSA384.Encrypted.txt

@@ -0,0 +1,11 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAMZphcrt
+UKJMlSabtzt2GdAAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlz
+dHAzODQAAABhBFL5NEL9uRhgkF2q+8m58EvtZq4mDGgcVEzafPRuNIn1018m9KuqNpOQ6d
++435n+MRYThe4MUdijSIDuopX2i14Z35oKZ9x2LsV+RxQczjmbnoWZdvgcvdOo6jiJdY7X
+JwAAAOBvXOaTq8vPRy7y5BBzr26QAYouJfGprYOqpywiIAZaICu0FJ8EXmmen6310CTG6Z
+CZ4VhC5MWCWRYTaOnPNn8FvGqo2bxEqWZmyZfVvv1Z35MtSAZEfwgfXaOZKJ/lPKsRndg5
+okpqNU1aG2u+4J7eZ7QyCD/1RCCEL5wwVcrDeuMkTDPpnJc1NEGz8HbfcZ5xZavrz6Wa9t
+tX7pFICqK9IIeOGMJ2WRXR6sQGyag+jNn9KmsIya7hkNJVeZeY2GKAk2s/0vxfYx9RFD55
+ewB34oHyTdxAQT3L+FZT6XfRHw==
+-----END OPENSSH PRIVATE KEY-----

+ 10 - 0
src/Data/Key.OPENSSH.ECDSA384.txt

@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
+1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRTP1DMXoHgW+RX+S/NxUEElou1cmLD
+6CEiR+zpzaGG6mzl6LhUY+Z3f2M3u4u7tcM8TgB/jiHbnI9TaeN5QK4HX1D9DXkH5RhfnL
+frm3kCTNoCFKd0Wa/QAvKrlNKiRi8AAADYlABjHJQAYxwAAAATZWNkc2Etc2hhMi1uaXN0
+cDM4NAAAAAhuaXN0cDM4NAAAAGEEUz9QzF6B4FvkV/kvzcVBBJaLtXJiw+ghIkfs6c2hhu
+ps5ei4VGPmd39jN7uLu7XDPE4Af44h25yPU2njeUCuB19Q/Q15B+UYX5y365t5AkzaAhSn
+dFmv0ALyq5TSokYvAAAAMAXLUgK32yWzUrpeLzpdFB2/eiNnkxQlu5OneTPukKcZYclfgo
+jv0YHK5LCvAtF8lwAAAAtzcmlua2VzQE5FTwECAwQF
+-----END OPENSSH PRIVATE KEY-----

+ 12 - 0
src/Data/Key.OPENSSH.ECDSA521.Encrypted.txt

@@ -0,0 +1,12 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBWjIyzbM
+MQ3UPE8BiQ0n4LAAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlz
+dHA1MjEAAACFBAH9BVM6bRhbELtgdMGsin5lM42R2EWoT+6Akakl5rQy2tHHLIYGLEfaqI
++0iUo2V6MxEf9w0hVz6SEsF+yDgyrYPQCIieaB1oBvIl+PZmL1XsuAXs2uMRsNJb4myGU/
+DiekxqzIPa0LMrBZ4xmErcn5Gazkw1EA0B3HoaW5wj+geI/efQAAARDi+GGTYH1T+5Dd8N
+EVCiL+J7fm8uP8yAcvQNh3JBYIf1g/GZ0hJDuA47fcTzXEfTGZLGWdgaE8cxIUICpjBoak
+EpNS1HyhqYZAt2J8o/14t2GbXczJfoQLOIQl2S1zXQ9shof12odu9DGcBhSAz9hswlndBE
+d99uCz/ymzwQ0i2Pp+urUXo7+YXB6HMh9YTMeGQAiDJFO3NPDqDczfUECtTUkQMhy8r06m
+hAp/oZ6K1KBbZzdc0xyqDePKAqqyHnN4FD7Wfv11SWoOhlUcEVg2ZvNj/O+CsoWzMpN+dt
+DPKZHmH/kegWKBsdtAC9f5Hg3b2oQAK0pKghms1+/J9iilnIMwv80CPzGdv0YAG9Vx5w==
+-----END OPENSSH PRIVATE KEY-----

+ 12 - 0
src/Data/Key.OPENSSH.ECDSA521.txt

@@ -0,0 +1,12 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
+1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQAa7p4WVga+08qu160BrdzKyRNJMQC
+eGhFluNdq73/uaW3qlqoEiaNtc5uB7HHyjCWQTmfzrSRLRZ7YBwUancwyh4Aq6gBgGsXVz
+wNvY3kxRDlGrSfMmsHlXz41dgw9wm1fBcf97niexRQ/xGlhkyf7IYlQ/s5BpXF2lS9l0H5
+hBippRgAAAEI/9prf//aa38AAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
+AAAIUEAGu6eFlYGvtPKrtetAa3cyskTSTEAnhoRZbjXau9/7mlt6paqBImjbXObgexx8ow
+lkE5n860kS0We2AcFGp3MMoeAKuoAYBrF1c8Db2N5MUQ5Rq0nzJrB5V8+NXYMPcJtXwXH/
+e54nsUUP8RpYZMn+yGJUP7OQaVxdpUvZdB+YQYqaUYAAAAQXwQnI20tNxwLKHPMDmumblD
+b0sBqW5Y9248L//x4sWFrkjk6k1LcZno9KLqz8+tIFMJ5sji+axRoUZCXb3cIPzPAAAAC3
+NyaW5rZXNATkVP
+-----END OPENSSH PRIVATE KEY-----

+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.OPENSSH.ED25519.Encrypted.txt → src/Data/Key.OPENSSH.ED25519.Encrypted.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.OPENSSH.ED25519.txt → src/Data/Key.OPENSSH.ED25519.txt


+ 28 - 0
src/Data/Key.OPENSSH.RSA.Encrypted.txt

@@ -0,0 +1,28 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCoWhCSaG
+psKT80oPIOAlqJAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCxOfnmxC1g
+mmX18LCBG/X73BoCXQEBEAJz3V9w0FMTgUBebK3fkfOMLNzWn5aMR608wQHOEPYhHffCJf
+dJR6lUWLHZz5+EZRfM9oHpysMtToYQoGtb9xM7D5J3lnWZgSea2R7xSeqpClN5nQUMGu8y
+2d2S3g9o1vrTdeu71u09QFOx2AXBPUmCjpCuBlNyYEEQMfRMtQ6PDbPdvLM1uylbQqB+/6
+jMsCyEFvlLit9GcZ7ItKQN+jsZNmP+f7ytVXLsZTjPLd5mWWrf6T1T1Xt9DBoLXnMrmDiC
+f/EXiTYonIO6B0FHvNUCrDzZ7rxIebqePLes1Q2yoUqmC8g+cww3AAADwNRNGSnB7cRpgU
+BxdyC0ofj0hUONXjmoT+OGPph9lgZMUnzcon9Z1bpsJuMoRXL14Cdbds7YPmw1YB94Uc+S
+8QexLJG0wGel2yvzJhU+QFsLeVRS4tayERFXGCoVpu7RunEYy+hvaiX5CD+luEkiarfj9I
+N8+9QUMhDYkELwWBV4rde18Vr8m1P1FuFgqikY0TfSKUGCkvjl4FvDxrxqsewaEkkzwRTI
+PhOFCCM5jBPWE+uWVcwKoidvAqcNbmwIzDNZGwXtrAvYYzZa62C/MNLHuFU1weuJiM8sYa
+6iKrk681BrrpGcSEZEXd41CFY3BWlIDTozrWn03xFlIpeLG2YMPcuYqFhR/41BJfa+fW5B
+Ei0SuUx2xjdRiamqpPku4H6ulkjl0KlFCr976Y2V1JZMQh7bd0huubmf4P4poBk6ZgGpSf
+snhcv1HjCVkvfA2yhOcXogzK2HOZgDS5sdSb/kUGURdjlj6ccSzc3OYaHAy9gZXj8Q58pA
+4KrXTDlCJ9BTR8PIND54j6gMKu5ijX0TP9nJf/hG9GXx+Xss8T3xdPxdNBapPCcuxGZGJN
+H+KFqrpmZYHm0evqFPS7BCUp2VvID6SMgrTYiH0IIbMHLStfdNchtn3EudMbW9vRhxg3Do
+npT7Px2JYp87PNoHg2eOx0yGy9r81n2+Wi7SpGCWD8MFfxqd4JIQ8+zjrIRAA1q53uuSUh
+m/hlmJWEjQWmcBw5bKrOU0CfGGoT3o6HWYRQ9d5+kKeoS+dOINxxf80G5b7vOrE3PbFxT3
+W8zwRd90Msr3LXgPaN0V4RJeBX38e0EvVbArL2MgSs/BC5aID0N0Sqiu+13AqqNYxj6RH2
+FA7FN+BBa16fvdi5h5kNnZUrQUKOAImjEE494O8uGKQImviGqB5PY6DJqHPTtn7RSwFx9E
+rR7nbAZPTucIN/OIfURxTedhROk0PXjWnwpjuz+UpaMRWqgWTv3bLOuqorqMLibAFLRQEQ
+6pR0wbmTpTfEW1jNmAohxB4N14YdSfhThzkCAgpQW6UCLc83y3EDzQFi5862a+2ixULKhK
+220tZRk2GU7OFAPRpgQ/sxptGqZbNdOV80wk1MgykoFkoptRkm7bfJcdLHZnP7E6yU0ssP
+rCbQlfD0/dD2QE/7HqxHsipNNuEagULjK6WUYXkpx1Siq2vecjZw8dNp7EBh+KlujEm+Dr
+R7KFdFCw8DUwrzXwfMIogeRVbW8H0/fQEqsX5oPLTEOnNBjzf8pHush7CCrprbo0ZK3xFp
+Vr3LUCoA==
+-----END OPENSSH PRIVATE KEY-----

+ 27 - 0
src/Data/Key.OPENSSH.RSA.txt

@@ -0,0 +1,27 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAQEA7W7Oigi7Hj1msa2l8HimLPzagVmE/CseMKCRxPMTtWTvoZdMt7hf
+kthWA7h20a7oSSeH2t7FqeOpVNGFYvRV3BVWsKZJMRcdJiTCZAjH38rYk90XvZ3mKrKN/3
+fcQsh4OiPnWqT6HSqg14oiV8UPtUwnE65skWSxvxt+ELlVpCqG5vE2O/GNDKQE7FzYt6vQ
+ihMFSABBqjvau0mjX002KYiCMr6vgl8XYkDA4n5+JyQSQxznnfrL93ZoaqujRP2bT5UhXg
+bzr8HFwuqQGvURECZTTI8Biv/6tOIn9x2hEaN6vxDLtXc99E2d6NW6cdriwaGFJ4jgVWDE
+5mU006XdPQAAA8jzN6zf8zes3wAAAAdzc2gtcnNhAAABAQDtbs6KCLsePWaxraXweKYs/N
+qBWYT8Kx4woJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkx
+Fx0mJMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/G3
+4QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdiQMDifn4n
+JBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q04if3HaERo3q/EM
+u1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09AAAAAwEAAQAAAQEA6Xgq+gppzOt9nrts
+z5Ajf1tHlSesn7XaYuCRVgPb3mOZSuEW3BUdTa0Sr2fk1nzSBpUrfqnN3idyK2g3bD1sbB
+RDgUKR+AaNcCN3TpxfxgyVeJhQLvEkEdovzQRUfwrXRfxmE3jkRGfVbvxylrG8p35xcmXy
+del46r2i8dj8gIY3tKp0RMvZ4ZlNbhWHPd5OxyHWL9e3gbOSyIyjQgTKZezhzErS/X/KJ/
+XYqzyBAqNqZ2Rg1kKiHRlHS6KEI2tyFwYfh+Rb6L9xch1SqOtQhTWirmxS25dpGD2jgalX
+eyiw8PmuqTiWCqUmUMx6MdF3tFsirr54K4QA9kqMeaRLtQAAAIEAsUQT0Uhq7l0ugTd4Y9
+89bH6eW0fol21/m7B5zkJQepNadUPTs188uvv4inW8n2O3RCanXWHZfCJ1AsR/MEEW2C6Q
+DtvqKXHbzfWQlCYSVxB17CjURKa8fNaIAk98zgmNNwO53NBleyrUhPgvm3xt7ACgpzXY5R
+wNJL8/a0WOmgwAAACBAP508Op6wWPAwn1JfBZuqQtjcfnJeN4NkYQBdybn0vVu5UdyqSQL
+a8hlAzROhA+qJvrlsZgM9h8CyLTyuim8likZHocwO13zBTdVaQ8c2lJvf2uXTIXNZHseS7
+ITkfBiO1hSB4z8RDkOr35mGfdbyJIFAwFZF4Xs8WnQF+vHEadrAAAAgQDu329eCwVFf9g0
+zNHfZu31p0WtErcsRv57fq+UoPtov8nxUF71oOWe5KSGnGtMICI31kBtPhUbvfOmuqNrgJ
+BjgjbPQmi0xSAE5L3QuEKRNjlaE3/WadKBwzhJDtauuYk1ifkrdAVp67XyQ5puyuGgVaQB
+NPbrxA9g1IbyeL4/9wAAAAtzcmlua2VzQE5FTwECAwQFBg==
+-----END OPENSSH PRIVATE KEY-----

+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.128.CBC.12345.txt → src/Data/Key.RSA.Encrypted.Aes.128.CBC.12345.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.192.CBC.12345.txt → src/Data/Key.RSA.Encrypted.Aes.192.CBC.12345.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.256.CBC.12345.txt → src/Data/Key.RSA.Encrypted.Aes.256.CBC.12345.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.CBC.12345.txt → src/Data/Key.RSA.Encrypted.Des.CBC.12345.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt → src/Data/Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt → src/Data/Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.RSA.txt → src/Data/Key.RSA.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt → src/Data/Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.SSH2.DSA.txt → src/Data/Key.SSH2.DSA.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt → src/Data/Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt


+ 0 - 0
src/Renci.SshNet.Tests/Data/Key.SSH2.RSA.txt → src/Data/Key.SSH2.RSA.txt


+ 66 - 0
src/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs

@@ -0,0 +1,66 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers;
+using Renci.SshNet.Common;
+using Renci.SshNet.Security;
+
+namespace Renci.SshNet.Benchmarks.Common
+{
+    [MemoryDiagnoser]
+    [ShortRunJob]
+    public class HostKeyEventArgsBenchmarks
+    {
+        private readonly KeyHostAlgorithm _keyHostAlgorithm;
+
+        public HostKeyEventArgsBenchmarks()
+        {
+            _keyHostAlgorithm = GetKeyHostAlgorithm();
+        }
+        private static KeyHostAlgorithm GetKeyHostAlgorithm()
+        {
+            using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
+            {
+                var privateKey = new PrivateKeyFile(s);
+                return (KeyHostAlgorithm) privateKey.HostKeyAlgorithms.First();
+            }
+        }
+
+        [Benchmark()]
+        public HostKeyEventArgs Constructor()
+        {
+            return new HostKeyEventArgs(_keyHostAlgorithm);
+        }
+
+        [Benchmark()]
+        public (string, string) CalculateFingerPrintSHA256AndMD5()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return (test.FingerPrintSHA256, test.FingerPrintMD5);
+        }
+
+        [Benchmark()]
+        public string CalculateFingerPrintSHA256()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return test.FingerPrintSHA256;
+        }
+
+        [Benchmark()]
+        public byte[] CalculateFingerPrint()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return test.FingerPrint;
+        }
+
+        [Benchmark()]
+        public string CalculateFingerPrintMD5()
+        {
+            var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+            return test.FingerPrintSHA256;
+        }
+    }
+}

+ 27 - 0
src/Renci.SshNet.Benchmarks/Program.cs

@@ -0,0 +1,27 @@
+using BenchmarkDotNet.Running;
+
+namespace Renci.SshNet.Benchmarks
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // Usage examples:
+            // 1. Run all benchmarks:
+            //     dotnet run -c Release -- --filter *
+            // 2. List all benchmarks:
+            //     dotnet run -c Release -- --list flat
+            // 3. Run a subset of benchmarks based on a filter (of a benchmark method's fully-qualified name,
+            //    e.g. "Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers.AesCipherBenchmarks.Encrypt_CBC"):
+            //     dotnet run -c Release -- --filter *Ciphers*
+            // 4. Run benchmarks and include memory usage statistics in the output:
+            //     dotnet run -c Release -- filter *Rsa* --memory
+            // 3. Print help:
+            //     dotnet run -c Release -- --help
+
+            // See also https://benchmarkdotnet.org/articles/guides/console-args.html
+
+            _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
+        }
+    }
+}

+ 31 - 0
src/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj

@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+      <!--
+          Even though we're not interested in producing XML docs for test projects, we have to enable this in order to have the .NET Compiler
+          Platform analyzers produce the IDE0005 (Remove unnecessary import) diagnostic.
+            
+          To avoid warnings for missing XML docs, we add CS1591 (Missing XML comment for publicly visible type or member) to the NoWarn property.
+          We can stop producing XML docs for test projects (and remove the NoWarn for CS1591) once the following issue is fixed:
+          https://github.com/dotnet/roslyn/issues/41640.
+      -->
+      <NoWarn>$(NoWarn);CS1591</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="BenchmarkDotNet" Version="0.13.8" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Renci.SshNet\Renci.SshNet.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+      <EmbeddedResource Include="..\Data\*.txt" LinkBase="Data" />
+  </ItemGroup>
+
+</Project>

+ 38 - 0
src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs

@@ -0,0 +1,38 @@
+using BenchmarkDotNet.Attributes;
+using Renci.SshNet.Security.Cryptography.Ciphers;
+using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
+{
+    [MemoryDiagnoser]
+    public class AesCipherBenchmarks
+    {
+        private readonly byte[] _key;
+        private readonly byte[] _iv;
+        private readonly byte[] _data;
+
+        public AesCipherBenchmarks()
+        {
+            _key = new byte[32];
+            _iv = new byte[16];
+            _data = new byte[256];
+
+            Random random = new(Seed: 12345);
+            random.NextBytes(_key);
+            random.NextBytes(_iv);
+            random.NextBytes(_data);
+        }
+
+        [Benchmark]
+        public byte[] Encrypt_CBC()
+        {
+            return new AesCipher(_key, new CbcCipherMode(_iv), null).Encrypt(_data);
+        }
+
+        [Benchmark]
+        public byte[] Decrypt_CBC()
+        {
+            return new AesCipher(_key, new CbcCipherMode(_iv), null).Decrypt(_data);
+        }
+    }
+}

+ 49 - 0
src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs

@@ -0,0 +1,49 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Security;
+using Renci.SshNet.Security.Cryptography.Ciphers;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
+{
+    [MemoryDiagnoser]
+    public class RsaCipherBenchmarks
+    {
+        private readonly RsaKey _privateKey;
+        private readonly RsaKey _publicKey;
+        private readonly byte[] _data;
+
+        public RsaCipherBenchmarks()
+        {
+            _data = new byte[128];
+
+            Random random = new(Seed: 12345);
+            random.NextBytes(_data);
+
+            using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
+            {
+                
+                _privateKey = (RsaKey)new PrivateKeyFile(s).Key;
+                
+                // The implementations of RsaCipher.Encrypt/Decrypt differ based on whether the supplied RsaKey has private key information
+                // or only public. So we extract out the public key information to a separate variable.
+                _publicKey = new RsaKey()
+                {
+                    Public = _privateKey.Public
+                };
+            }
+        }
+
+        [Benchmark]
+        public byte[] Encrypt()
+        {
+            return new RsaCipher(_publicKey).Encrypt(_data);
+        }
+
+        // RSA Decrypt does not work
+        // [Benchmark]
+        // public byte[] Decrypt()
+        // {
+        //     return new RsaCipher(_privateKey).Decrypt(_data);
+        // }
+    }
+}

+ 41 - 0
src/Renci.SshNet.Benchmarks/Security/Cryptography/ED25519DigitalSignatureBenchmarks.cs

@@ -0,0 +1,41 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Security;
+using Renci.SshNet.Security.Cryptography;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography
+{
+    [MemoryDiagnoser]
+    public class ED25519DigitalSignatureBenchmarks
+    {
+        private readonly ED25519Key _key;
+        private readonly byte[] _data;
+        private readonly byte[] _signature;
+
+        public ED25519DigitalSignatureBenchmarks()
+        {
+            _data = new byte[128];
+
+            Random random = new(Seed: 12345);
+            random.NextBytes(_data);
+
+            using (var s = typeof(ED25519DigitalSignatureBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.OPENSSH.ED25519.txt"))
+            {
+                _key = (ED25519Key) new PrivateKeyFile(s).Key;
+            }
+            _signature = new ED25519DigitalSignature(_key).Sign(_data);
+        }
+
+        [Benchmark]
+        public byte[] Sign()
+        {
+            return new ED25519DigitalSignature(_key).Sign(_data);
+        }
+
+        [Benchmark]
+        public bool Verify()
+        {
+            return new ED25519DigitalSignature(_key).Verify(_data, _signature);
+        }
+    }
+}

+ 32 - 0
src/Renci.SshNet.IntegrationTests/.editorconfig

@@ -0,0 +1,32 @@
+[*.cs]
+
+#### SYSLIB diagnostics ####
+
+# SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time
+#
+# TODO: Remove this when https://github.com/sshnet/SSH.NET/issues/1131 is implemented.
+dotnet_diagnostic.SYSLIB1045.severity = none
+
+### StyleCop Analyzers rules ###
+
+#### .NET Compiler Platform analysers rules ####
+
+# IDE0007: Use var instead of explicit type
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0007
+dotnet_diagnostic.IDE0007.severity = suggestion
+
+# IDE0028: Use collection initializers
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0028
+dotnet_diagnostic.IDE0028.severity = suggestion
+
+# IDE0058: Remove unnecessary expression value
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0058
+dotnet_diagnostic.IDE0058.severity = suggestion
+
+# IDE0059: Remove unnecessary value assignment
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0059
+dotnet_diagnostic.IDE0059.severity = suggestion
+
+# IDE0230: Use UTF-8 string literal
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0230
+dotnet_diagnostic.IDE0230.severity = suggestion

+ 1 - 0
src/Renci.SshNet.IntegrationTests/.gitignore

@@ -0,0 +1 @@
+TestResults/

+ 23 - 0
src/Renci.SshNet.IntegrationTests/App.config

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <system.diagnostics>
+    <trace autoflush="true"/>
+    <sources>
+      <source name="SshNet.Logging" switchName="SshNetSwitch" switchType="System.Diagnostics.SourceSwitch">
+        <listeners>
+          <!--<add name="SshDotNetTraceFile" />-->
+          <!--<add name="Console"/>-->
+        </listeners>
+      </source>
+    </sources>
+    <switches>
+      <add name="SshNetSwitch" value="Verbose"/>
+    </switches>
+    <sharedListeners>
+      <add name="SshDotNetTraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="SshNetTrace.log">
+        <!--<filter type="System.Diagnostics.EventTypeFilter" initializeData="Warning" />-->
+      </add>
+      <add name="Console" type="System.Diagnostics.ConsoleTraceListener" traceOutputOptions="DateTime,Timestamp,ThreadId"/>
+    </sharedListeners>
+  </system.diagnostics>
+</configuration>

+ 84 - 0
src/Renci.SshNet.IntegrationTests/AuthenticationMethodFactory.cs

@@ -0,0 +1,84 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    public class AuthenticationMethodFactory
+    {
+        public PasswordAuthenticationMethod CreatePowerUserPasswordAuthenticationMethod()
+        {
+            var user = Users.Admin;
+            return new PasswordAuthenticationMethod(user.UserName, user.Password);
+        }
+
+        public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyAuthenticationMethod()
+        {
+            var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa");
+            return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
+        }
+
+        public PrivateKeyAuthenticationMethod CreateRegularUserMultiplePrivateKeyAuthenticationMethod()
+        {
+            var privateKeyFile1 = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa");
+            var privateKeyFile2 = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa");
+            return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile1, privateKeyFile2);
+        }
+
+        public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyWithPassPhraseAuthenticationMethod()
+        {
+            var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa_with_pass", "tester");
+            return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
+        }
+
+        public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyWithEmptyPassPhraseAuthenticationMethod()
+        {
+            var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa_with_pass", null);
+            return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
+        }
+
+        public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey()
+        {
+            var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_noaccess.rsa");
+            return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
+        }
+
+        public PasswordAuthenticationMethod CreateRegulatUserPasswordAuthenticationMethod()
+        {
+            return new PasswordAuthenticationMethod(Users.Regular.UserName, Users.Regular.Password);
+        }
+
+        public PasswordAuthenticationMethod CreateRegularUserPasswordAuthenticationMethodWithBadPassword()
+        {
+            return new PasswordAuthenticationMethod(Users.Regular.UserName, "xxx");
+        }
+
+        public KeyboardInteractiveAuthenticationMethod CreateRegularUserKeyboardInteractiveAuthenticationMethod()
+        {
+            var keyboardInteractive = new KeyboardInteractiveAuthenticationMethod(Users.Regular.UserName);
+            keyboardInteractive.AuthenticationPrompt += (sender, args) =>
+                {
+                    foreach (var authenticationPrompt in args.Prompts)
+                    {
+                        authenticationPrompt.Response = Users.Regular.Password;
+                    }
+                };
+            return keyboardInteractive;
+        }
+
+        private PrivateKeyFile GetPrivateKey(string resourceName, string passPhrase = null)
+        {
+            using (var stream = GetResourceStream(resourceName))
+            {
+                return new PrivateKeyFile(stream, passPhrase);
+            }
+        }
+
+        private Stream GetResourceStream(string resourceName)
+        {
+            var type = GetType();
+            var resourceStream = type.Assembly.GetManifestResourceStream(resourceName);
+            if (resourceStream == null)
+            {
+                throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName));
+            }
+            return resourceStream;
+        }
+    }
+}

+ 427 - 0
src/Renci.SshNet.IntegrationTests/AuthenticationTests.cs

@@ -0,0 +1,427 @@
+using Renci.SshNet.Common;
+using Renci.SshNet.IntegrationTests.Common;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class AuthenticationTests : IntegrationTestBase
+    {
+        private AuthenticationMethodFactory _authenticationMethodFactory;
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private IConnectionInfoFactory _adminConnectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _authenticationMethodFactory = new AuthenticationMethodFactory();
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort, _authenticationMethodFactory);
+            _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
+            _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+
+            using (var client = new SshClient(_adminConnectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                // Reset the password back to the "regular" password.
+                using (var cmd = client.RunCommand($"echo \"{Users.Regular.Password}\n{Users.Regular.Password}\" | sudo passwd " + Users.Regular.UserName))
+                {
+                    Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+                }
+
+                // Remove password expiration
+                using (var cmd = client.RunCommand($"sudo chage --expiredate -1 " + Users.Regular.UserName))
+                {
+                    Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_PublicKey()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Authentication")]
+        public void Multifactor_PublicKey_Connect_Then_Reconnect()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+                client.Disconnect();
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_PublicKeyWithPassPhrase()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyWithPassPhraseAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        [ExpectedException(typeof(SshPassPhraseNullOrEmptyException))]
+        public void Multifactor_PublicKeyWithEmptyPassPhrase()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyWithEmptyPassPhraseAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_PublicKey_MultiplePrivateKey()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserMultiplePrivateKeyAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_PublicKey_MultipleAuthenticationMethod()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod(),
+                                                               _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_KeyboardInteractiveAndPublicKey()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive,publickey")
+                             .WithChallengeResponseAuthentication(true)
+                             .WithKeyboardInteractiveAuthentication(true)
+                             .WithUsePAM(true)
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(),
+                                                               _authenticationMethodFactory.CreateRegularUserKeyboardInteractiveAuthenticationMethod(),
+                                                               _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_Password_ExceedsPartialSuccessLimit()
+        {
+            // configure server to require more successfull authentications from a given method than our partial
+            // success limit (5) allows
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password,password,password,password,password")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                try
+                {
+                    client.Connect();
+                    Assert.Fail();
+                }
+                catch (SshAuthenticationException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("Reached authentication attempt limit for method (password).", ex.Message);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_Password_MatchPartialSuccessLimit()
+        {
+            // configure server to require a number of successfull authentications from a given method that exactly
+            // matches our partial success limit (5)
+
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password,password,password,password")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,keyboard-interactive")
+                             .WithChallengeResponseAuthentication(true)
+                             .WithKeyboardInteractiveAuthentication(true)
+                             .WithUsePAM(true)
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod(),
+                                                               _authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_Password_Or_PublicKeyAndPassword_BadPassword()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,password")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(),
+                                                               _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                try
+                {
+                    client.Connect();
+                    Assert.Fail();
+                }
+                catch (SshAuthenticationException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("Permission denied (password).", ex.Message);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Multifactor_PasswordAndPublicKey_Or_PasswordAndPassword()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,publickey password,password")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(),
+                                                               _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+
+            connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(),
+                                                               _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                try
+                {
+                    client.Connect();
+                    Assert.Fail();
+                }
+                catch (SshAuthenticationException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("Permission denied (password).", ex.Message);
+                }
+            }
+
+        }
+
+        [TestMethod]
+        public void Multifactor_PasswordAndPassword_Or_PublicKey()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password publickey")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(),
+                                                               _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+
+            connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+
+        }
+
+        [TestMethod]
+        public void Multifactor_Password_Or_Password()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password password")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+
+            connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(),
+                                                           _authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        [TestMethod]
+        public void KeyboardInteractive_PasswordExpired()
+        {
+            var temporaryPassword = new Random().Next().ToString();
+
+            using (var client = new SshClient(_adminConnectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                // Temporarity modify password so that when we expire this password, we change reset the password back to
+                // the "regular" password.
+                using (var cmd = client.RunCommand($"echo \"{temporaryPassword}\n{temporaryPassword}\" | sudo passwd " + Users.Regular.UserName))
+                {
+                    Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+                }
+
+                // Force the password to expire immediately
+                using (var cmd = client.RunCommand($"sudo chage -d 0 " + Users.Regular.UserName))
+                {
+                    Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+                }
+            }
+
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
+                             .WithChallengeResponseAuthentication(true)
+                             .WithKeyboardInteractiveAuthentication(true)
+                             .WithUsePAM(true)
+                             .Update()
+                             .Restart();
+
+            var keyboardInteractive = new KeyboardInteractiveAuthenticationMethod(Users.Regular.UserName);
+            int authenticationPromptCount = 0;
+            keyboardInteractive.AuthenticationPrompt += (sender, args) =>
+            {
+                Console.WriteLine(args.Instruction);
+                foreach (var authenticationPrompt in args.Prompts)
+                {
+                    Console.WriteLine(authenticationPrompt.Request);
+                    switch (authenticationPromptCount)
+                    {
+                        case 0:
+                            // Regular password prompt
+                            authenticationPrompt.Response = temporaryPassword;
+                            break;
+                        case 1:
+                            // Password expired, provide current password
+                            authenticationPrompt.Response = temporaryPassword;
+                            break;
+                        case 2:
+                            // Password expired, provide new password
+                            authenticationPrompt.Response = Users.Regular.Password;
+                            break;
+                        case 3:
+                            // Password expired, retype new password
+                            authenticationPrompt.Response = Users.Regular.Password;
+                            break;
+                    }
+
+                    authenticationPromptCount++;
+                }
+            };
+
+            var connectionInfo = _connectionInfoFactory.Create(keyboardInteractive);
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+                Assert.AreEqual(4, authenticationPromptCount);
+            }
+        }
+
+        [TestMethod]
+        public void KeyboardInteractiveConnectionInfo()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
+                             .WithChallengeResponseAuthentication(true)
+                             .WithKeyboardInteractiveAuthentication(true)
+                             .WithUsePAM(true)
+                             .Update()
+                             .Restart();
+
+            var host = SshServerHostName;
+            var port = SshServerPort;
+            var username = User.UserName;
+            var password = User.Password;
+
+            #region Example KeyboardInteractiveConnectionInfo AuthenticationPrompt
+
+            var connectionInfo = new KeyboardInteractiveConnectionInfo(host, port, username);
+            connectionInfo.AuthenticationPrompt += delegate (object sender, AuthenticationPromptEventArgs e)
+                                                       {
+                                                           Console.WriteLine(e.Instruction);
+
+                                                           foreach (var prompt in e.Prompts)
+                                                           {
+                                                               Console.WriteLine(prompt.Request);
+                                                               prompt.Response = password;
+                                                           }
+                                                       };
+
+            using (var client = new SftpClient(connectionInfo))
+            {
+                client.Connect();
+
+                //  Do something here
+                client.Disconnect();
+            }
+
+            #endregion
+
+            Assert.AreEqual(connectionInfo.Host, SshServerHostName);
+            Assert.AreEqual(connectionInfo.Username, User.UserName);
+        }
+    }
+}

+ 81 - 0
src/Renci.SshNet.IntegrationTests/CipherTests.cs

@@ -0,0 +1,81 @@
+using Renci.SshNet.IntegrationTests.Common;
+using Renci.SshNet.TestTools.OpenSSH;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class CipherTests : IntegrationTestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+        }
+
+        [TestMethod]
+        public void TripledesCbc()
+        {
+            DoTest(Cipher.TripledesCbc);
+        }
+
+        [TestMethod]
+        public void Aes128Cbc()
+        {
+            DoTest(Cipher.Aes128Cbc);
+        }
+
+        [TestMethod]
+        public void Aes192Cbc()
+        {
+            DoTest(Cipher.Aes192Cbc);
+        }
+
+        [TestMethod]
+        public void Aes256Cbc()
+        {
+            DoTest(Cipher.Aes256Cbc);
+        }
+
+        [TestMethod]
+        public void Aes128Ctr()
+        {
+            DoTest(Cipher.Aes128Ctr);
+        }
+
+        [TestMethod]
+        public void Aes192Ctr()
+        {
+            DoTest(Cipher.Aes192Ctr);
+        }
+
+        [TestMethod]
+        public void Aes256Ctr()
+        {
+            DoTest(Cipher.Aes256Ctr);
+        }
+
+        private void DoTest(Cipher cipher)
+        {
+            _remoteSshdConfig.ClearCiphers()
+                             .AddCipher(cipher)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+    }
+}

+ 32 - 0
src/Renci.SshNet.IntegrationTests/Common/ArrayBuilder.cs

@@ -0,0 +1,32 @@
+namespace Renci.SshNet.IntegrationTests.Common
+{
+    public class ArrayBuilder<T>
+    {
+        private readonly List<T> _buffer;
+
+        public ArrayBuilder()
+        {
+            _buffer = new List<T>();
+        }
+
+        public ArrayBuilder<T> Add(T[] array)
+        {
+            return Add(array, 0, array.Length);
+        }
+
+        public ArrayBuilder<T> Add(T[] array, int index, int length)
+        {
+            for (var i = 0; i < length; i++)
+            {
+                _buffer.Add(array[index + i]);
+            }
+
+            return this;
+        }
+
+        public T[] Build()
+        {
+            return _buffer.ToArray();
+        }
+    }
+}

+ 393 - 0
src/Renci.SshNet.IntegrationTests/Common/AsyncSocketListener.cs

@@ -0,0 +1,393 @@
+using System.Net;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.IntegrationTests.Common
+{
+    public class AsyncSocketListener : IDisposable
+    {
+        private readonly IPEndPoint _endPoint;
+        private readonly ManualResetEvent _acceptCallbackDone;
+        private readonly List<Socket> _connectedClients;
+        private readonly object _syncLock;
+        private Socket _listener;
+        private Thread _receiveThread;
+        private bool _started;
+        private string _stackTrace;
+
+        public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket);
+        public delegate void ConnectedHandler(Socket socket);
+
+        public event BytesReceivedHandler BytesReceived;
+        public event ConnectedHandler Connected;
+        public event ConnectedHandler Disconnected;
+
+        public AsyncSocketListener(IPEndPoint endPoint)
+        {
+            _endPoint = endPoint;
+            _acceptCallbackDone = new ManualResetEvent(false);
+            _connectedClients = new List<Socket>();
+            _syncLock = new object();
+            ShutdownRemoteCommunicationSocket = true;
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether the <see cref="Socket.Shutdown(SocketShutdown)"/> is invoked on the <see cref="Socket"/>
+        /// that is used to handle the communication with the remote host, when the remote host has closed the connection.
+        /// </summary>
+        /// <value>
+        /// <see langword="true"/> to invoke <see cref="Socket.Shutdown(SocketShutdown)"/> on the <see cref="Socket"/> that is used
+        /// to handle the communication with the remote host, when the remote host has closed the connection; otherwise,
+        /// <see langword="false"/>. The default is <see langword="true"/>.
+        /// </value>
+        public bool ShutdownRemoteCommunicationSocket { get; set; }
+
+        public void Start()
+        {
+            _listener = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+            _listener.Bind(_endPoint);
+            _listener.Listen(1);
+
+            _started = true;
+
+            _receiveThread = new Thread(StartListener);
+            _receiveThread.Start(_listener);
+
+            _stackTrace = Environment.StackTrace;
+        }
+
+        public void Stop()
+        {
+            _started = false;
+
+            lock (_syncLock)
+            {
+                foreach (var connectedClient in _connectedClients)
+                {
+                    try
+                    {
+                        connectedClient.Shutdown(SocketShutdown.Send);
+                    }
+                    catch (Exception ex)
+                    {
+                        Console.Error.WriteLine("[{0}] Failure shutting down socket: {1}",
+                                                typeof(AsyncSocketListener).FullName,
+                                                ex);
+                    }
+
+                    DrainSocket(connectedClient);
+                    connectedClient.Dispose();
+                }
+
+                _connectedClients.Clear();
+            }
+
+            _listener?.Dispose();
+
+            if (_receiveThread != null)
+            {
+                _receiveThread.Join();
+                _receiveThread = null;
+            }
+        }
+
+        public void Dispose()
+        {
+            Stop();
+            GC.SuppressFinalize(this);
+        }
+
+        private void StartListener(object state)
+        {
+            try
+            {
+                var listener = (Socket) state;
+                while (_started)
+                {
+                    _ = _acceptCallbackDone.Reset();
+                    _ = listener.BeginAccept(AcceptCallback, listener);
+                    _ = _acceptCallbackDone.WaitOne();
+                }
+            }
+            catch (Exception ex)
+            {
+                // On .NET framework when Thread throws an exception then unit tests
+                // were executed without any problem.
+                // On new .NET exceptions from Thread breaks unit tests session.
+                Console.Error.WriteLine("[{0}] Failure in StartListener: {1}",
+                    typeof(AsyncSocketListener).FullName,
+                    ex);
+            }
+        }
+
+        private void AcceptCallback(IAsyncResult ar)
+        {
+            // Signal the main thread to continue
+            _ = _acceptCallbackDone.Set();
+
+            // Get the socket that listens for inbound connections
+            var listener = (Socket) ar.AsyncState;
+
+            // Get the socket that handles the client request
+            Socket handler;
+
+            try
+            {
+                handler = listener.EndAccept(ar);
+            }
+            catch (SocketException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.EndAccept(...) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}",
+                        typeof(AsyncSocketListener).FullName,
+                        ex);
+                }
+                return;
+            }
+            catch (ObjectDisposedException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.EndAccept(IAsyncResult) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}",
+                        typeof(AsyncSocketListener).FullName,
+                        ex);
+                }
+                return;
+            }
+
+            // Signal new connection
+            SignalConnected(handler);
+
+            lock (_syncLock)
+            {
+                // Register client socket
+                _connectedClients.Add(handler);
+            }
+
+            var state = new SocketStateObject(handler);
+
+            try
+            {
+                _ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
+            }
+            catch (SocketException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.BeginReceive(...) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
+                        typeof(AsyncSocketListener).FullName,
+                        ex);
+                }
+            }
+            catch (ObjectDisposedException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.BeginReceive(...) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
+                                            typeof(AsyncSocketListener).FullName,
+                                            ex);
+                }
+            }
+        }
+
+        private void ReadCallback(IAsyncResult ar)
+        {
+            // Retrieve the state object and the handler socket
+            // from the asynchronous state object
+            var state = (SocketStateObject) ar.AsyncState;
+            var handler = state.Socket;
+
+            int bytesRead;
+            try
+            {
+                // Read data from the client socket.
+                bytesRead = handler.EndReceive(ar, out var errorCode);
+                if (errorCode != SocketError.Success)
+                {
+                    bytesRead = 0;
+                }
+            }
+            catch (SocketException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.EndReceive(...) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
+                                            typeof(AsyncSocketListener).FullName,
+                                            ex);
+                }
+                return;
+            }
+            catch (ObjectDisposedException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.EndReceive(...) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
+                                            typeof(AsyncSocketListener).FullName,
+                                            ex);
+                }
+                return;
+            }
+
+            void ConnectionDisconnected()
+            {
+                SignalDisconnected(handler);
+
+                if (ShutdownRemoteCommunicationSocket)
+                {
+                    lock (_syncLock)
+                    {
+                        if (!_started)
+                        {
+                            return;
+                        }
+
+                        try
+                        {
+                            handler.Shutdown(SocketShutdown.Send);
+                            handler.Close();
+                        }
+                        catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
+                        {
+                            // On .NET 7 we got Socker Exception with ConnectionReset from Shutdown method
+                            // when the socket is disposed
+                        }
+                        catch (SocketException ex)
+                        {
+                            throw new Exception("Exception in ReadCallback: " + ex.SocketErrorCode + " " + _stackTrace, ex);
+                        }
+                        catch (Exception ex)
+                        {
+                            throw new Exception("Exception in ReadCallback: " + _stackTrace, ex);
+                        }
+
+                        _ = _connectedClients.Remove(handler);
+                    }
+                }
+            }
+
+            if (bytesRead > 0)
+            {
+                var bytesReceived = new byte[bytesRead];
+                Array.Copy(state.Buffer, bytesReceived, bytesRead);
+                SignalBytesReceived(bytesReceived, handler);
+
+                try
+                {
+                    _ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
+                }
+                catch (ObjectDisposedException)
+                {
+                    // TODO On .NET 7, sometimes we get ObjectDisposedException when _started but only on appveyor, locally it works
+                    ConnectionDisconnected();
+                }
+                catch (SocketException ex)
+                {
+                    if (!_started)
+                    {
+                        throw new Exception("BeginReceive while stopping!", ex);
+                    }
+
+                    throw new Exception("BeginReceive while started!: " + ex.SocketErrorCode + " " + _stackTrace, ex);
+                }
+
+            }
+            else
+            {
+                ConnectionDisconnected();
+            }
+        }
+
+        private void SignalBytesReceived(byte[] bytesReceived, Socket client)
+        {
+            BytesReceived?.Invoke(bytesReceived, client);
+        }
+
+        private void SignalConnected(Socket client)
+        {
+            Connected?.Invoke(client);
+        }
+
+        private void SignalDisconnected(Socket client)
+        {
+            Disconnected?.Invoke(client);
+        }
+
+        private static void DrainSocket(Socket socket)
+        {
+            var buffer = new byte[128];
+
+            try
+            {
+                while (true && socket.Connected)
+                {
+                    var bytesRead = socket.Receive(buffer);
+                    if (bytesRead == 0)
+                    {
+                        break;
+                    }
+                }
+            }
+            catch (SocketException ex)
+            {
+                Console.Error.WriteLine("[{0}] Failure draining socket ({1}): {2}",
+                                        typeof(AsyncSocketListener).FullName,
+                                        ex.SocketErrorCode.ToString("G"),
+                                        ex);
+            }
+        }
+
+        private class SocketStateObject
+        {
+            public Socket Socket { get; private set; }
+
+            public readonly byte[] Buffer = new byte[1024];
+
+            public SocketStateObject(Socket handler)
+            {
+                Socket = handler;
+            }
+        }
+    }
+}

+ 11 - 0
src/Renci.SshNet.IntegrationTests/Common/DateTimeAssert.cs

@@ -0,0 +1,11 @@
+namespace Renci.SshNet.IntegrationTests.Common
+{
+    public static class DateTimeAssert
+    {
+        public static void AreEqual(DateTime expected, DateTime actual)
+        {
+            Assert.AreEqual(expected, actual, $"Expected {expected:o}, but was {actual:o}.");
+            Assert.AreEqual(expected.Kind, actual.Kind);
+        }
+    }
+}

+ 12 - 0
src/Renci.SshNet.IntegrationTests/Common/DateTimeExtensions.cs

@@ -0,0 +1,12 @@
+namespace Renci.SshNet.IntegrationTests.Common
+{
+    public static class DateTimeExtensions
+    {
+        public static DateTime TruncateToWholeSeconds(this DateTime dateTime)
+        {
+            return dateTime.AddMilliseconds(-dateTime.Millisecond)
+                           .AddMicroseconds(-dateTime.Microsecond)
+                           .AddTicks(-(dateTime.Nanosecond / 100));
+        }
+    }
+}

+ 30 - 0
src/Renci.SshNet.IntegrationTests/Common/RemoteSshdConfigExtensions.cs

@@ -0,0 +1,30 @@
+using Renci.SshNet.TestTools.OpenSSH;
+
+namespace Renci.SshNet.IntegrationTests.Common
+{
+    internal static class RemoteSshdConfigExtensions
+    {
+        private const string DefaultAuthenticationMethods = "password publickey";
+
+        public static void Reset(this RemoteSshdConfig remoteSshdConfig)
+        {
+            remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, DefaultAuthenticationMethods)
+                            .WithChallengeResponseAuthentication(false)
+                            .WithKeyboardInteractiveAuthentication(false)
+                            .PrintMotd()
+                            .WithLogLevel(LogLevel.Debug3)
+                            .ClearHostKeyFiles()
+                            .AddHostKeyFile(HostKeyFile.Rsa.FilePath)
+                            .ClearSubsystems()
+                            .AddSubsystem(new Subsystem("sftp", "/usr/lib/ssh/sftp-server"))
+                            .ClearCiphers()
+                            .ClearKeyExchangeAlgorithms()
+                            .ClearHostKeyAlgorithms()
+                            .ClearPublicKeyAcceptedAlgorithms()
+                            .ClearMessageAuthenticationCodeAlgorithms()
+                            .WithUsePAM(true)
+                            .Update()
+                            .Restart();
+        }
+    }
+}

+ 255 - 0
src/Renci.SshNet.IntegrationTests/Common/Socks5Handler.cs

@@ -0,0 +1,255 @@
+using System.Net;
+using System.Net.Sockets;
+
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Transport;
+
+namespace Renci.SshNet.IntegrationTests.Common
+{
+    class Socks5Handler
+    {
+        private readonly IPEndPoint _proxyEndPoint;
+        private readonly string _userName;
+        private readonly string _password;
+
+        public Socks5Handler(IPEndPoint proxyEndPoint, string userName, string password)
+        {
+            _proxyEndPoint = proxyEndPoint;
+            _userName = userName;
+            _password = password;
+        }
+
+        public Socket Connect(IPEndPoint endPoint)
+        {
+            if (endPoint == null)
+            {
+                throw new ArgumentNullException("endPoint");
+            }
+
+            var addressBytes = GetAddressBytes(endPoint);
+            return Connect(addressBytes, endPoint.Port);
+        }
+
+        public Socket Connect(string host, int port)
+        {
+            if (host == null)
+            {
+                throw new ArgumentNullException(nameof(host));
+            }
+
+            if (host.Length > byte.MaxValue)
+            {
+                throw new ArgumentException($@"Cannot be more than {byte.MaxValue} characters.", nameof(host));
+            }
+
+            var addressBytes = new byte[host.Length + 2];
+            addressBytes[0] = 0x03;
+            addressBytes[1] = (byte) host.Length;
+            Encoding.ASCII.GetBytes(host, 0, host.Length, addressBytes, 2);
+            return Connect(addressBytes, port);
+        }
+
+        private Socket Connect(byte[] addressBytes, int port)
+        {
+            var socket = SocketAbstraction.Connect(_proxyEndPoint, TimeSpan.FromSeconds(5));
+
+            //  Send socks version number
+            SocketWriteByte(socket, 0x05);
+
+            //  Send number of supported authentication methods
+            SocketWriteByte(socket, 0x02);
+
+            //  Send supported authentication methods
+            SocketWriteByte(socket, 0x00); //  No authentication
+            SocketWriteByte(socket, 0x02); //  Username/Password
+
+            var socksVersion = SocketReadByte(socket);
+            if (socksVersion != 0x05)
+            {
+                throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
+            }
+
+            var authenticationMethod = SocketReadByte(socket);
+            switch (authenticationMethod)
+            {
+                case 0x00:
+                    break;
+                case 0x02:
+
+                    //  Send version
+                    SocketWriteByte(socket, 0x01);
+
+                    var username = Encoding.ASCII.GetBytes(_userName);
+                    if (username.Length > byte.MaxValue)
+                    {
+                        throw new ProxyException("Proxy username is too long.");
+                    }
+
+                    //  Send username length
+                    SocketWriteByte(socket, (byte) username.Length);
+
+                    //  Send username
+                    SocketAbstraction.Send(socket, username);
+
+                    var password = Encoding.ASCII.GetBytes(_password);
+
+                    if (password.Length > byte.MaxValue)
+                    {
+                        throw new ProxyException("Proxy password is too long.");
+                    }
+
+                    //  Send username length
+                    SocketWriteByte(socket, (byte) password.Length);
+
+                    //  Send username
+                    SocketAbstraction.Send(socket, password);
+
+                    var serverVersion = SocketReadByte(socket);
+
+                    if (serverVersion != 1)
+                    {
+                        throw new ProxyException("SOCKS5: Server authentication version is not valid.");
+                    }
+
+                    var statusCode = SocketReadByte(socket);
+                    if (statusCode != 0)
+                    {
+                        throw new ProxyException("SOCKS5: Username/Password authentication failed.");
+                    }
+
+                    break;
+                case 0xFF:
+                    throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
+                default:
+                    throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
+            }
+
+            //  Send socks version number
+            SocketWriteByte(socket, 0x05);
+
+            //  Send command code
+            SocketWriteByte(socket, 0x01); //  establish a TCP/IP stream connection
+
+            //  Send reserved, must be 0x00
+            SocketWriteByte(socket, 0x00);
+
+            //  Send address type and address
+            SocketAbstraction.Send(socket, addressBytes);
+
+            //  Send port
+            SocketWriteByte(socket, (byte)(port / 0xFF));
+            SocketWriteByte(socket, (byte)(port % 0xFF));
+
+            //  Read Server SOCKS5 version
+            if (SocketReadByte(socket) != 5)
+            {
+                throw new ProxyException("SOCKS5: Version 5 is expected.");
+            }
+
+            //  Read response code
+            var status = SocketReadByte(socket);
+
+            switch (status)
+            {
+                case 0x00:
+                    break;
+                case 0x01:
+                    throw new ProxyException("SOCKS5: General failure.");
+                case 0x02:
+                    throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
+                case 0x03:
+                    throw new ProxyException("SOCKS5: Network unreachable.");
+                case 0x04:
+                    throw new ProxyException("SOCKS5: Host unreachable.");
+                case 0x05:
+                    throw new ProxyException("SOCKS5: Connection refused by destination host.");
+                case 0x06:
+                    throw new ProxyException("SOCKS5: TTL expired.");
+                case 0x07:
+                    throw new ProxyException("SOCKS5: Command not supported or protocol error.");
+                case 0x08:
+                    throw new ProxyException("SOCKS5: Address type not supported.");
+                default:
+                    throw new ProxyException("SOCKS4: Not valid response.");
+            }
+
+            //  Read 0
+            if (SocketReadByte(socket) != 0)
+            {
+                throw new ProxyException("SOCKS5: 0 byte is expected.");
+            }
+
+            var addressType = SocketReadByte(socket);
+            var responseIp = new byte[16];
+
+            switch (addressType)
+            {
+                case 0x01:
+                    SocketRead(socket, responseIp, 0, 4);
+                    break;
+                case 0x04:
+                    SocketRead(socket, responseIp, 0, 16);
+                    break;
+                default:
+                    throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
+            }
+
+            var portBytes = new byte[2];
+
+            //  Read 2 bytes to be ignored
+            SocketRead(socket, portBytes, 0, 2);
+
+            return socket;
+        }
+
+        private static byte[] GetAddressBytes(IPEndPoint endPoint)
+        {
+            if (endPoint.AddressFamily == AddressFamily.InterNetwork)
+            {
+                var addressBytes = new byte[4 + 1];
+                addressBytes[0] = 0x01;
+                var address = endPoint.Address.GetAddressBytes();
+                Buffer.BlockCopy(address, 0, addressBytes, 1, address.Length);
+                return addressBytes;
+            }
+
+            if (endPoint.AddressFamily == AddressFamily.InterNetworkV6)
+            {
+                var addressBytes = new byte[16 + 1];
+                addressBytes[0] = 0x04;
+                var address = endPoint.Address.GetAddressBytes();
+                Buffer.BlockCopy(address, 0, addressBytes, 1, address.Length);
+                return addressBytes;
+            }
+
+            throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", endPoint.Address));
+        }
+
+        private static void SocketWriteByte(Socket socket, byte data)
+        {
+            SocketAbstraction.Send(socket, new[] { data });
+        }
+
+        private static byte SocketReadByte(Socket socket)
+        {
+            var buffer = new byte[1];
+            SocketRead(socket, buffer, 0, 1);
+            return buffer[0];
+        }
+
+        private static int SocketRead(Socket socket, byte[] buffer, int offset, int length)
+        {
+            var bytesRead = SocketAbstraction.Read(socket, buffer, offset, length, TimeSpan.FromMilliseconds(-1));
+            if (bytesRead == 0)
+            {
+                // when we're in the disconnecting state (either triggered by client or server), then the
+                // SshConnectionException will interrupt the message listener loop (if not already interrupted)
+                // and the exception itself will be ignored (in RaiseError)
+                throw new SshConnectionException("An established connection was aborted by the server.",
+                    DisconnectReason.ConnectionLost);
+            }
+            return bytesRead;
+        }
+    }
+}

+ 462 - 0
src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs

@@ -0,0 +1,462 @@
+using Renci.SshNet.Common;
+using Renci.SshNet.IntegrationTests.Common;
+using Renci.SshNet.Messages.Transport;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class ConnectivityTests : IntegrationTestBase
+    {
+        private AuthenticationMethodFactory _authenticationMethodFactory;
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private IConnectionInfoFactory _adminConnectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+        private SshConnectionDisruptor _sshConnectionDisruptor;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _authenticationMethodFactory = new AuthenticationMethodFactory();
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort, _authenticationMethodFactory);
+            _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
+            _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
+            _sshConnectionDisruptor = new SshConnectionDisruptor(_adminConnectionInfoFactory);
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+        }
+
+        [TestMethod]
+        public void Common_CreateMoreChannelsThanMaxSessions()
+        {
+            var connectionInfo = _connectionInfoFactory.Create();
+            connectionInfo.MaxSessions = 2;
+
+            using (var client = new SshClient(connectionInfo))
+            {
+                client.Connect();
+
+                // create one more channel than the maximum number of sessions
+                // as that would block indefinitely when creating the last channel
+                // if the channel would not be properly closed
+                for (var i = 0; i < connectionInfo.MaxSessions + 1; i++)
+                {
+                    using (var stream = client.CreateShellStream("vt220", 20, 20, 20, 20, 0))
+                    {
+                        stream.WriteLine("echo test");
+                        stream.ReadLine();
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Common_DisposeAfterLossOfNetworkConnectivity()
+        {
+            var hostNetworkConnectionDisabled = false;
+            SshConnectionRestorer disruptor = null;
+            try
+            {
+                Exception errorOccurred = null;
+                int count = 0;
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.ErrorOccurred += (sender, args) =>
+                                                {
+                                                    Console.WriteLine("Exception " + count++);
+                                                    Console.WriteLine(args.Exception);
+                                                    errorOccurred = args.Exception;
+                                                };
+                    client.Connect();
+
+                    disruptor = _sshConnectionDisruptor.BreakConnections();
+                    hostNetworkConnectionDisabled = true;
+                    WaitForConnectionInterruption(client);
+                }
+                
+                Assert.IsNotNull(errorOccurred);
+                Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
+
+                var connectionException = (SshConnectionException) errorOccurred;
+                Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
+                Assert.IsNull(connectionException.InnerException);
+                Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
+            }
+            finally
+            {
+                if (hostNetworkConnectionDisabled)
+                {
+                    disruptor?.RestoreConnections();
+                    disruptor?.Dispose();
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Common_DetectLossOfNetworkConnectivityThroughKeepAlive()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                Exception errorOccurred = null;
+                int count = 0;
+                client.ErrorOccurred += (sender, args) =>
+                                            {
+                                                Console.WriteLine("Exception "+ count++);
+                                                Console.WriteLine(args.Exception);
+                                                errorOccurred = args.Exception;
+                                            };
+                client.KeepAliveInterval = new TimeSpan(0, 0, 0, 0, 50);
+                client.Connect();
+
+                var disruptor = _sshConnectionDisruptor.BreakConnections();
+
+                try
+                {
+                    WaitForConnectionInterruption(client);
+
+                    Assert.IsFalse(client.IsConnected);
+
+                    Assert.IsNotNull(errorOccurred);
+                    Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
+
+                    var connectionException = (SshConnectionException) errorOccurred;
+                    Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
+                    Assert.IsNull(connectionException.InnerException);
+                    Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
+                }
+                finally
+                {
+                    disruptor?.RestoreConnections();
+                    disruptor?.Dispose();
+                }
+            }
+        }
+
+        private static void WaitForConnectionInterruption(SftpClient client)
+        {
+            for (var i = 0; i < 500; i++)
+            {
+                if (!client.IsConnected)
+                {
+                    break;
+                }
+
+                Thread.Sleep(100);
+            }
+
+            // After interruption, you have to wait for the events to propagate.
+            Thread.Sleep(100);
+        }
+
+        [TestMethod]
+        public void Common_DetectConnectionResetThroughSftpInvocation()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.KeepAliveInterval = TimeSpan.FromSeconds(1);
+                client.OperationTimeout = TimeSpan.FromSeconds(60);
+                ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
+                Exception errorOccurred = null;
+                client.ErrorOccurred += (sender, args) =>
+                {
+                    errorOccurred = args.Exception;
+                    errorOccurredSignaled.Set();
+                };
+                client.Connect();
+
+                var disruptor = _sshConnectionDisruptor.BreakConnections();
+
+                try
+                {
+                    client.ListDirectory("/");
+                    Assert.Fail();
+                }
+                catch (SshConnectionException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("Client not connected.", ex.Message);
+
+                    Assert.IsNotNull(errorOccurred);
+                    Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
+
+                    var connectionException = (SshConnectionException) errorOccurred;
+                    Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
+                    Assert.IsNull(connectionException.InnerException);
+                    Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
+                }
+                finally
+                {
+                    disruptor.RestoreConnections();
+                    disruptor.Dispose();
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Common_LossOfNetworkConnectivityDisconnectAndConnect()
+        {
+            bool vmNetworkConnectionDisabled = false;
+            SshConnectionRestorer disruptor = null;
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    Exception errorOccurred = null;
+                    client.ErrorOccurred += (sender, args) => errorOccurred = args.Exception;
+
+                    client.Connect();
+
+                    disruptor = _sshConnectionDisruptor.BreakConnections();
+                    vmNetworkConnectionDisabled = true;
+
+                    WaitForConnectionInterruption(client);
+                    // disconnect while network connectivity is lost
+                    client.Disconnect();
+
+                    Assert.IsFalse(client.IsConnected);
+
+                    disruptor.RestoreConnections();
+                    vmNetworkConnectionDisabled = false;
+
+                    // connect when network connectivity is restored
+                    client.Connect();
+                    client.ChangeDirectory(client.WorkingDirectory);
+                    client.Dispose();
+
+                    Assert.IsNotNull(errorOccurred);
+                    Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
+
+                    var connectionException = (SshConnectionException) errorOccurred;
+                    Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
+                    Assert.IsNull(connectionException.InnerException);
+                    Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
+                }
+            }
+            finally
+            {
+                if (vmNetworkConnectionDisabled)
+                {
+                    disruptor.RestoreConnections();
+                }
+                disruptor?.Dispose();
+            }
+        }
+
+        [TestMethod]
+        public void Common_DetectLossOfNetworkConnectivityThroughSftpInvocation()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
+                Exception errorOccurred = null;
+                client.ErrorOccurred += (sender, args) =>
+                {
+                    errorOccurred = args.Exception;
+                    errorOccurredSignaled.Set();
+                };
+                client.Connect();
+
+                var disruptor = _sshConnectionDisruptor.BreakConnections();
+                Thread.Sleep(100);
+                try
+                {
+                    client.ListDirectory("/");
+                    Assert.Fail();
+                }
+                catch (SshConnectionException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("Client not connected.", ex.Message);
+
+                    Assert.IsNotNull(errorOccurred);
+                    Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
+
+                    var connectionException = (SshConnectionException) errorOccurred;
+                    Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
+                    Assert.IsNull(connectionException.InnerException);
+                    Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
+                }
+                finally
+                {
+                    disruptor.RestoreConnections();
+                    disruptor.Dispose();
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Common_DetectSessionKilledOnServer()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
+                Exception errorOccurred = null;
+                client.ErrorOccurred += (sender, args) =>
+                {
+                    errorOccurred = args.Exception;
+                    errorOccurredSignaled.Set();
+                };
+                client.Connect();
+
+                // Kill the server session
+                using (var adminClient = new SshClient(_adminConnectionInfoFactory.Create()))
+                {
+                    adminClient.Connect();
+
+                    var command = $"sudo ps --no-headers -u {client.ConnectionInfo.Username} -f | grep \"{client.ConnectionInfo.Username}@notty\" | awk '{{print $2}}' | xargs sudo kill -9";
+                    var sshCommand = adminClient.CreateCommand(command);
+                    var result = sshCommand.Execute();
+                    Assert.AreEqual(0, sshCommand.ExitStatus, sshCommand.Error);
+                }
+
+                Assert.IsTrue(errorOccurredSignaled.WaitOne(200));
+                Assert.IsNotNull(errorOccurred);
+                Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
+                Assert.IsNull(errorOccurred.InnerException);
+                Assert.AreEqual("An established connection was aborted by the server.", errorOccurred.Message);
+                Assert.IsFalse(client.IsConnected);
+            }
+        }
+
+        [TestMethod]
+        public void Common_HostKeyValidation_Failure()
+        {
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.HostKeyReceived += (sender, e) => { e.CanTrust = false; };
+
+                try
+                {
+                    client.Connect();
+                    Assert.Fail();
+                }
+                catch (SshConnectionException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("Key exchange negotiation failed.", ex.Message);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Common_HostKeyValidation_Success()
+        {
+            byte[] host_rsa_key_openssh_fingerprint =
+                {
+                    0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13,
+                    0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b
+                };
+
+            var hostValidationSuccessful = false;
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.HostKeyReceived += (sender, e) =>
+                {
+                    if (host_rsa_key_openssh_fingerprint.Length == e.FingerPrint.Length)
+                    {
+                        for (var i = 0; i < host_rsa_key_openssh_fingerprint.Length; i++)
+                        {
+                            if (host_rsa_key_openssh_fingerprint[i] != e.FingerPrint[i])
+                            {
+                                e.CanTrust = false;
+                                break;
+                            }
+                        }
+
+                        hostValidationSuccessful = e.CanTrust;
+                    }
+                    else
+                    {
+                        e.CanTrust = false;
+                    }
+                };
+                client.Connect();
+            }
+
+            Assert.IsTrue(hostValidationSuccessful);
+        }
+
+        [TestMethod]
+        public void Common_HostKeyValidationSHA256_Success()
+        {
+            var hostValidationSuccessful = false;
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.HostKeyReceived += (sender, e) =>
+                {
+                    if (e.FingerPrintSHA256 == "9fa6vbz64gimzsGZ/xZi3aaYE1o7E96iU2NjcfQNGwI")
+                    {
+                        hostValidationSuccessful = e.CanTrust;
+                    }
+                    else
+                    {
+                        e.CanTrust = false;
+                    }
+                };
+                client.Connect();
+            }
+
+            Assert.IsTrue(hostValidationSuccessful);
+        }
+
+        [TestMethod]
+        public void Common_HostKeyValidationMD5_Success()
+        {
+            var hostValidationSuccessful = false;
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.HostKeyReceived += (sender, e) =>
+                {
+                    if (e.FingerPrintMD5 == "3d:90:d8:0d:d5:e0:b6:13:42:7c:78:1e:19:a3:99:2b")
+                    {
+                        hostValidationSuccessful = e.CanTrust;
+                    }
+                    else
+                    {
+                        e.CanTrust = false;
+                    }
+                };
+                client.Connect();
+            }
+
+            Assert.IsTrue(hostValidationSuccessful);
+        }
+        /// <summary>
+        /// Verifies whether we handle a disconnect initiated by the SSH server (through a SSH_MSG_DISCONNECT message).
+        /// </summary>
+        /// <remarks>
+        /// We force this by only configuring <c>keyboard-interactive</c> as authentication method, while <c>ChallengeResponseAuthentication</c>
+        /// is not enabled.  This causes OpenSSH to terminate the connection because there are no authentication methods left.
+        /// </remarks>
+        [TestMethod]
+        public void Common_ServerRejectsConnection()
+        {
+            _remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserKeyboardInteractiveAuthenticationMethod());
+            using (var client = new SftpClient(connectionInfo))
+            {
+                try
+                {
+                    client.Connect();
+                    Assert.Fail();
+                }
+                catch (SshConnectionException ex)
+                {
+                    Assert.AreEqual(DisconnectReason.ProtocolError, ex.DisconnectReason);
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("The connection was closed by the server: no authentication methods enabled (ProtocolError).", ex.Message);
+                }
+            }
+        }
+
+    }
+}

+ 14 - 0
src/Renci.SshNet.IntegrationTests/Credential.cs

@@ -0,0 +1,14 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    internal class Credential
+    {
+        public Credential(string userName, string password)
+        {
+            UserName = userName;
+            Password = password;
+        }
+
+        public string UserName { get; }
+        public string Password { get; }
+    }
+}

+ 50 - 0
src/Renci.SshNet.IntegrationTests/Dockerfile

@@ -0,0 +1,50 @@
+FROM alpine:latest
+
+COPY --chown=root:root server/ssh /etc/ssh/
+COPY --chown=root:root server/script /opt/sshnet
+COPY user/sshnet /home/sshnet/.ssh
+
+RUN apk update && apk upgrade --no-cache && \
+    apk add --no-cache syslog-ng && \
+    # install and configure sshd
+    apk add --no-cache openssh && \
+    # install openssh-server-pam to allow for keyboard-interactive authentication
+    apk add --no-cache openssh-server-pam && \
+    dos2unix /etc/ssh/* && \
+    chmod 400 /etc/ssh/ssh*key && \
+    sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
+    sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
+    # Set the default RSA key
+    echo 'HostKey /etc/ssh/ssh_host_rsa_key' >> /etc/ssh/sshd_config && \
+    chmod 646 /etc/ssh/sshd_config && \
+    # install and configure sudo
+    apk add --no-cache sudo && \
+    addgroup sudo && \
+    # allow root to run any command
+    echo 'root ALL=(ALL) ALL' > /etc/sudoers && \
+    # allow everyone in the 'sudo' group to run any command without a password
+    echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
+    # add user to run most of the integration tests
+    adduser -D sshnet && \
+    passwd -u sshnet && \
+    echo 'sshnet:ssh4ever' | chpasswd && \
+    dos2unix /home/sshnet/.ssh/* && \
+    chown -R sshnet:sshnet /home/sshnet && \
+    chmod -R 700 /home/sshnet/.ssh && \
+    chmod -R 644 /home/sshnet/.ssh/authorized_keys && \
+    # add user to administer container (update configs, restart sshd)
+    adduser -D sshnetadm && \
+    passwd -u sshnetadm && \
+    echo 'sshnetadm:ssh4ever' | chpasswd && \
+    addgroup sshnetadm sudo && \
+    dos2unix /opt/sshnet/* && \
+    # install shadow package; we use chage command in this package to expire/unexpire password of the sshnet user
+    apk add --no-cache shadow && \
+    # allow us to use telnet command; we use this in the remote port forwarding tests
+    apk --no-cache add busybox-extras && \
+    # install full-fledged ps command
+    apk add --no-cache procps
+
+EXPOSE 22 22
+
+ENTRYPOINT ["/opt/sshnet/start.sh"]

+ 75 - 0
src/Renci.SshNet.IntegrationTests/HmacTests.cs

@@ -0,0 +1,75 @@
+using Renci.SshNet.IntegrationTests.Common;
+using Renci.SshNet.TestTools.OpenSSH;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class HmacTests : IntegrationTestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+        }
+
+        [TestMethod]
+        public void HmacMd5()
+        {
+            DoTest(MessageAuthenticationCodeAlgorithm.HmacMd5);
+        }
+
+        [TestMethod]
+        public void HmacMd5_96()
+        {
+            DoTest(MessageAuthenticationCodeAlgorithm.HmacMd5_96);
+        }
+
+        [TestMethod]
+        public void HmacSha1()
+        {
+            DoTest(MessageAuthenticationCodeAlgorithm.HmacSha1);
+        }
+
+        [TestMethod]
+        public void HmacSha1_96()
+        {
+            DoTest(MessageAuthenticationCodeAlgorithm.HmacSha1_96);
+        }
+
+        [TestMethod]
+        public void HmacSha2_256()
+        {
+            DoTest(MessageAuthenticationCodeAlgorithm.HmacSha2_256);
+        }
+
+        [TestMethod]
+        public void HmacSha2_512()
+        {
+            DoTest(MessageAuthenticationCodeAlgorithm.HmacSha2_512);
+        }
+
+        private void DoTest(MessageAuthenticationCodeAlgorithm macAlgorithm)
+        {
+            _remoteSshdConfig.ClearMessageAuthenticationCodeAlgorithms()
+                             .AddMessageAuthenticationCodeAlgorithm(macAlgorithm)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+    }
+}

+ 115 - 0
src/Renci.SshNet.IntegrationTests/HostConfig.cs

@@ -0,0 +1,115 @@
+using System.Net;
+using System.Text.RegularExpressions;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    class HostConfig
+    {
+        private static readonly Regex HostsEntryRegEx = new Regex(@"^(?<IPAddress>[\S]+)\s+(?<HostName>[a-zA-Z]+[a-zA-Z\-\.]*[a-zA-Z]+)\s*(?<Aliases>.+)*$", RegexOptions.Singleline);
+
+        public List<HostEntry> Entries { get; }
+
+        private HostConfig()
+        {
+            Entries = new List<HostEntry>();
+        }
+
+        public static HostConfig Read(ScpClient scpClient, string path)
+        {
+            HostConfig hostConfig = new HostConfig();
+
+            using (var ms = new MemoryStream())
+            {
+                scpClient.Download(path, ms);
+                ms.Position = 0;
+
+                using (var sr = new StreamReader(ms, Encoding.ASCII))
+                {
+                    string line;
+                    while ((line = sr.ReadLine()) != null)
+                    {
+                        // skip comments
+                        if (line.StartsWith("#"))
+                        {
+                            continue;
+                        }
+
+                        var hostEntryMatch = HostsEntryRegEx.Match(line);
+                        if (!hostEntryMatch.Success)
+                        {
+                            continue;
+                        }
+
+                        var entryIPAddress = hostEntryMatch.Groups["IPAddress"].Value;
+                        var entryAliasesGroup = hostEntryMatch.Groups["Aliases"];
+
+                        var entry = new HostEntry(IPAddress.Parse(entryIPAddress), hostEntryMatch.Groups["HostName"].Value);
+
+                        if (entryAliasesGroup.Success)
+                        {
+                            var aliases = entryAliasesGroup.Value.Split(' ');
+                            foreach (var alias in aliases)
+                            {
+                                entry.Aliases.Add(alias);
+                            }
+                        }
+
+                        hostConfig.Entries.Add(entry);
+                    }
+                }
+            }
+
+            return hostConfig;
+        }
+
+        public void Write(ScpClient scpClient, string path)
+        {
+            using (var ms = new MemoryStream())
+            using (var sw = new StreamWriter(ms, Encoding.ASCII))
+            {
+                // Use linux line ending
+                sw.NewLine = "\n";
+                     
+                foreach (var hostEntry in Entries)
+                {
+                    sw.Write(hostEntry.IPAddress);
+                    sw.Write("    ");
+                    sw.Write(hostEntry.HostName);
+
+                    if (hostEntry.Aliases.Count > 0)
+                    {
+                        sw.Write("    ");
+                        for (var i = 0; i < hostEntry.Aliases.Count; i++)
+                        {
+                            if (i > 0)
+                            {
+                                sw.Write(' ');
+                            }
+                            sw.Write(hostEntry.Aliases[i]);
+                        }
+                    }
+                    sw.WriteLine();
+                }
+
+                sw.Flush();
+                ms.Position = 0;
+
+                scpClient.Upload(ms, path);
+            }
+        }
+    }
+
+    public class HostEntry
+    {
+        public HostEntry(IPAddress ipAddress, string hostName)
+        {
+            IPAddress = ipAddress;
+            HostName = hostName;
+            Aliases = new List<string>();
+        }
+
+        public IPAddress IPAddress { get; private set; }
+        public string HostName { get; set; }
+        public List<string> Aliases { get; }
+    }
+}

+ 80 - 0
src/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs

@@ -0,0 +1,80 @@
+using Renci.SshNet.Common;
+using Renci.SshNet.IntegrationTests.Common;
+using Renci.SshNet.TestTools.OpenSSH;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class HostKeyAlgorithmTests : IntegrationTestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+        }
+
+        [TestMethod]
+        public void SshDss()
+        {
+            DoTest(HostKeyAlgorithm.SshDss, HostKeyFile.Dsa, 2048);
+        }
+
+        [TestMethod]
+        public void SshRsa()
+        {
+            DoTest(HostKeyAlgorithm.SshRsa, HostKeyFile.Rsa, 3072);
+        }
+
+        [TestMethod]
+        public void SshRsaSha256()
+        {
+            DoTest(HostKeyAlgorithm.RsaSha2256, HostKeyFile.Rsa, 3072);
+        }
+
+        [TestMethod]
+        public void SshRsaSha512()
+        {
+            DoTest(HostKeyAlgorithm.RsaSha2512, HostKeyFile.Rsa, 3072);
+        }
+
+        [TestMethod]
+        public void SshEd25519()
+        {
+            DoTest(HostKeyAlgorithm.SshEd25519, HostKeyFile.Ed25519, 256);
+        }
+
+        private void DoTest(HostKeyAlgorithm hostKeyAlgorithm, HostKeyFile hostKeyFile, int keyLength)
+        {
+            _remoteSshdConfig.ClearHostKeyAlgorithms()
+                             .AddHostKeyAlgorithm(hostKeyAlgorithm)
+                             .ClearHostKeyFiles()
+                             .AddHostKeyFile(hostKeyFile.FilePath)
+                             .Update()
+                             .Restart();
+
+            HostKeyEventArgs hostKeyEventsArgs = null;
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e;
+                client.Connect();
+                client.Disconnect();
+            }
+
+            Assert.IsNotNull(hostKeyEventsArgs);
+            Assert.AreEqual(hostKeyAlgorithm.Name, hostKeyEventsArgs.HostKeyName);
+            Assert.AreEqual(keyLength, hostKeyEventsArgs.KeyLength);
+            CollectionAssert.AreEqual(hostKeyFile.FingerPrint, hostKeyEventsArgs.FingerPrint);
+        }
+    }
+}

+ 23 - 0
src/Renci.SshNet.IntegrationTests/HostKeyFile.cs

@@ -0,0 +1,23 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    public sealed class HostKeyFile
+    {
+        public static readonly HostKeyFile Rsa = new HostKeyFile("ssh-rsa", "/etc/ssh/ssh_host_rsa_key", new byte[] { 0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13, 0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b });
+        public static readonly HostKeyFile Dsa = new HostKeyFile("ssh-dsa", "/etc/ssh/ssh_host_dsa_key", new byte[] { 0x50, 0xe0, 0xd5, 0x11, 0xf7, 0xed, 0x54, 0x75, 0x0d, 0x03, 0xc6, 0x52, 0x9b, 0x3b, 0x3c, 0x9f });
+        public static readonly HostKeyFile Ed25519 = new HostKeyFile("ssh-ed25519", "/etc/ssh/ssh_host_ed25519_key", new byte[] { 0xb3, 0xb9, 0xd0, 0x1b, 0x73, 0xc4, 0x60, 0xb4, 0xce, 0xed, 0x06, 0xf8, 0x58, 0x49, 0xa3, 0xda });
+        public const string Ecdsa = "/etc/ssh/ssh_host_ecdsa_key";
+
+        private HostKeyFile(string keyName, string filePath, byte[] fingerPrint)
+        {
+            KeyName = keyName;
+            FilePath = filePath;
+            FingerPrint = fingerPrint;
+        }
+
+        public string KeyName {get; }
+        public string FilePath { get; }
+        public byte[] FingerPrint { get; }
+    }
+
+
+}

+ 10 - 0
src/Renci.SshNet.IntegrationTests/IConnectionInfoFactory.cs

@@ -0,0 +1,10 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    public interface IConnectionInfoFactory
+    {
+        ConnectionInfo Create();
+        ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods);
+        ConnectionInfo CreateWithProxy();
+        ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods);
+    }
+}

+ 206 - 0
src/Renci.SshNet.IntegrationTests/KeyExchangeAlgorithmTests.cs

@@ -0,0 +1,206 @@
+using Renci.SshNet.IntegrationTests.Common;
+using Renci.SshNet.TestTools.OpenSSH;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class KeyExchangeAlgorithmTests : IntegrationTestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+        }
+
+        [TestMethod]
+        public void Curve25519Sha256()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.Curve25519Sha256)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Curve25519Sha256Libssh()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.Curve25519Sha256Libssh)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void DiffieHellmanGroup1Sha1()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup1Sha1)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void DiffieHellmanGroup14Sha1()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup14Sha1)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void DiffieHellmanGroup14Sha256()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup14Sha256)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void DiffieHellmanGroup16Sha512()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup16Sha512)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [Ignore]
+        public void DiffieHellmanGroup18Sha512()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup18Sha512)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void DiffieHellmanGroupExchangeSha1()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroupExchangeSha1)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void DiffieHellmanGroupExchangeSha256()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroupExchangeSha256)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void EcdhSha2Nistp256()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp256)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void EcdhSha2Nistp384()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp384)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void EcdhSha2Nistp521()
+        {
+            _remoteSshdConfig.ClearKeyExchangeAlgorithms()
+                             .AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp521)
+                             .Update()
+                             .Restart();
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+                client.Disconnect();
+            }
+        }
+    }
+}

+ 36 - 0
src/Renci.SshNet.IntegrationTests/LinuxAdminConnectionFactory.cs

@@ -0,0 +1,36 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    public class LinuxAdminConnectionFactory : IConnectionInfoFactory
+    {
+        private readonly string _host;
+        private readonly int _port;
+
+        public LinuxAdminConnectionFactory(string sshServerHostName, ushort sshServerPort)
+        {
+            _host = sshServerHostName;
+            _port = sshServerPort;
+        }
+
+        public ConnectionInfo Create()
+        {
+            var user = Users.Admin;
+            return new ConnectionInfo(_host, _port, user.UserName, new PasswordAuthenticationMethod(user.UserName, user.Password));
+        }
+
+        public ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods)
+        {
+            throw new NotImplementedException();
+        }
+
+        public ConnectionInfo CreateWithProxy()
+        {
+            throw new NotImplementedException();
+        }
+
+        public ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
+

+ 62 - 0
src/Renci.SshNet.IntegrationTests/LinuxVMConnectionFactory.cs

@@ -0,0 +1,62 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    public class LinuxVMConnectionFactory : IConnectionInfoFactory
+    {
+        
+
+        private const string ProxyHost = "127.0.0.1";
+        private const int ProxyPort = 1234;
+        private const string ProxyUserName = "test";
+        private const string ProxyPassword = "123";
+        private readonly string _host;
+        private readonly int _port;
+        private readonly AuthenticationMethodFactory _authenticationMethodFactory;
+
+
+        public LinuxVMConnectionFactory(string sshServerHostName, ushort sshServerPort)
+        {
+            _host = sshServerHostName;
+            _port = sshServerPort;
+
+            _authenticationMethodFactory = new AuthenticationMethodFactory();
+        }
+
+        public LinuxVMConnectionFactory(string sshServerHostName, ushort sshServerPort, AuthenticationMethodFactory authenticationMethodFactory)
+        {
+            _host = sshServerHostName;
+            _port = sshServerPort;
+
+            _authenticationMethodFactory = authenticationMethodFactory;
+        }
+
+        public ConnectionInfo Create()
+        {
+            return Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+        }
+
+        public ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods)
+        {
+            return new ConnectionInfo(_host, _port, Users.Regular.UserName, authenticationMethods);
+        }
+
+        public ConnectionInfo CreateWithProxy()
+        {
+            return CreateWithProxy(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
+        }
+
+        public ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods)
+        {
+            return new ConnectionInfo(
+                _host,
+                _port,
+                Users.Regular.UserName,
+                ProxyTypes.Socks4,
+                ProxyHost,
+                ProxyPort,
+                ProxyUserName,
+                ProxyPassword,
+                authenticationMethods);
+        }
+    }
+}
+

+ 158 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ForwardedPortLocalTest.cs

@@ -0,0 +1,158 @@
+using System.Diagnostics;
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{    
+    /// <summary>
+    /// Provides functionality for local port forwarding
+    /// </summary>
+    [TestClass]
+    public class ForwardedPortLocalTest : IntegrationTestBase
+    {
+        [TestMethod]
+        [WorkItem(713)]
+        [Owner("Kenneth_aa")]
+        [TestCategory("PortForwarding")]
+        [Description("Test if calling Stop on ForwardedPortLocal instance causes wait.")]
+        public void Test_PortForwarding_Local_Stop_Hangs_On_Wait()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+
+                var port1 = new ForwardedPortLocal("localhost", 8084, "www.google.com", 80);
+                client.AddForwardedPort(port1);
+                port1.Exception += delegate (object sender, ExceptionEventArgs e)
+                {
+                    Assert.Fail(e.Exception.ToString());
+                };
+
+                port1.Start();
+
+                var hasTestedTunnel = false;
+
+                _ = ThreadPool.QueueUserWorkItem(delegate (object state)
+                {
+                    try
+                    {
+                        var url = "http://www.google.com/";
+                        Debug.WriteLine("Starting web request to \"" + url + "\"");
+
+#if NET6_0_OR_GREATER
+                        var httpClient = new HttpClient();
+                        var response = httpClient.GetAsync(url)
+                                                 .ConfigureAwait(false)
+                                                 .GetAwaiter()
+                                                 .GetResult();
+#else
+                            var request = (HttpWebRequest) WebRequest.Create(url);
+                            var response = (HttpWebResponse) request.GetResponse();
+#endif // NET6_0_OR_GREATER
+
+                        Assert.IsNotNull(response);
+
+                        Debug.WriteLine("Http Response status code: " + response.StatusCode.ToString());
+
+                        response.Dispose();
+
+                        hasTestedTunnel = true;
+                    }
+                    catch (Exception ex)
+                    {
+                        Assert.Fail(ex.ToString());
+                    }
+                });
+
+                // Wait for the web request to complete.
+                while (!hasTestedTunnel)
+                {
+                    Thread.Sleep(1000);
+                }
+
+                try
+                {
+                    // Try stop the port forwarding, wait 3 seconds and fail if it is still started.
+                    _ = ThreadPool.QueueUserWorkItem(delegate (object state)
+                    {
+                        Debug.WriteLine("Trying to stop port forward.");
+                        port1.Stop();
+                        Debug.WriteLine("Port forwarding stopped.");
+                    });
+
+                    Thread.Sleep(3000);
+                    if (port1.IsStarted)
+                    {
+                        Assert.Fail("Port forwarding not stopped.");
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Assert.Fail(ex.ToString());
+                }
+                client.RemoveForwardedPort(port1);
+                client.Disconnect();
+                Debug.WriteLine("Success.");
+            }
+        }
+
+        [TestMethod]
+        [ExpectedException(typeof(SshConnectionException))]
+        public void Test_PortForwarding_Local_Without_Connecting()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                var port1 = new ForwardedPortLocal("localhost", 8084, "www.renci.org", 80);
+                client.AddForwardedPort(port1);
+                port1.Exception += delegate (object sender, ExceptionEventArgs e)
+                {
+                    Assert.Fail(e.Exception.ToString());
+                };
+                port1.Start();
+
+                var test = Parallel.For(0,
+                                 100,
+                                 counter =>
+                                 {
+                                     var start = DateTime.Now;
+
+#if NET6_0_OR_GREATER
+                                     var httpClient = new HttpClient();
+                                     using (var response = httpClient.GetAsync("http://localhost:8084").GetAwaiter().GetResult())
+                                     {
+                                         var data = ReadStream(response.Content.ReadAsStream());
+#else
+                                        var request = (HttpWebRequest) WebRequest.Create("http://localhost:8084");
+                                        using (var response = (HttpWebResponse) request.GetResponse())
+                                        {
+                                            var data = ReadStream(response.GetResponseStream());
+#endif // NET6_0_OR_GREATER
+                                         var end = DateTime.Now;
+
+                                         Debug.WriteLine(string.Format("Request# {2}: Lenght: {0} Time: {1}", data.Length, end - start, counter));
+                                     }
+                                 });
+            }
+        }
+
+        private static byte[] ReadStream(Stream stream)
+        {
+            var buffer = new byte[1024];
+            using (var ms = new MemoryStream())
+            {
+                while (true)
+                {
+                    var read = stream.Read(buffer, 0, buffer.Length);
+                    if (read > 0)
+                    {
+                        ms.Write(buffer, 0, read);
+                    }
+                    else
+                    {
+                        return ms.ToArray();
+                    }
+                }
+            }
+        }
+    }
+}

+ 336 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/ScpClientTest.cs

@@ -0,0 +1,336 @@
+using System.Security.Cryptography;
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Provides SCP client functionality.
+    /// </summary>
+    [TestClass]
+    public partial class ScpClientTest : IntegrationTestBase
+    {
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_File_Upload_Download()
+        {
+            RemoveAllFiles();
+
+            using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                scp.Connect();
+
+                var uploadedFileName = Path.GetTempFileName();
+                var downloadedFileName = Path.GetTempFileName();
+
+                CreateTestFile(uploadedFileName, 1);
+
+                scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName));
+
+                scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName));
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_Stream_Upload_Download()
+        {
+            RemoveAllFiles();
+
+            using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                scp.Connect();
+
+                var uploadedFileName = Path.GetTempFileName();
+                var downloadedFileName = Path.GetTempFileName();
+
+                CreateTestFile(uploadedFileName, 1);
+
+                //  Calculate has value
+                using (var stream = File.OpenRead(uploadedFileName))
+                {
+                    scp.Upload(stream, Path.GetFileName(uploadedFileName));
+                }
+
+                using (var stream = File.OpenWrite(downloadedFileName))
+                {
+                    scp.Download(Path.GetFileName(uploadedFileName), stream);
+                }
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_10MB_File_Upload_Download()
+        {
+            RemoveAllFiles();
+
+            using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                scp.Connect();
+
+                var uploadedFileName = Path.GetTempFileName();
+                var downloadedFileName = Path.GetTempFileName();
+
+                CreateTestFile(uploadedFileName, 10);
+
+                scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName));
+
+                scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName));
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_10MB_Stream_Upload_Download()
+        {
+            RemoveAllFiles();
+
+            using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                scp.Connect();
+
+                var uploadedFileName = Path.GetTempFileName();
+                var downloadedFileName = Path.GetTempFileName();
+
+                CreateTestFile(uploadedFileName, 10);
+
+                //  Calculate has value
+                using (var stream = File.OpenRead(uploadedFileName))
+                {
+                    scp.Upload(stream, Path.GetFileName(uploadedFileName));
+                }
+
+                using (var stream = File.OpenWrite(downloadedFileName))
+                {
+                    scp.Download(Path.GetFileName(uploadedFileName), stream);
+                }
+
+                //  Calculate MD5 value
+                var uploadedHash = CalculateMD5(uploadedFileName);
+                var downloadedHash = CalculateMD5(downloadedFileName);
+
+                File.Delete(uploadedFileName);
+                File.Delete(downloadedFileName);
+
+                scp.Disconnect();
+
+                Assert.AreEqual(uploadedHash, downloadedHash);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_Directory_Upload_Download()
+        {
+            RemoveAllFiles();
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+                sftp.CreateDirectory("uploaded_dir");
+            }
+
+            using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                scp.Connect();
+
+                var uploadDirectory =
+                    Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
+                for (var i = 0; i < 3; i++)
+                {
+                    var subfolder = Directory.CreateDirectory(string.Format(@"{0}\folder_{1}", uploadDirectory.FullName, i));
+
+                    for (var j = 0; j < 5; j++)
+                    {
+                        CreateTestFile(string.Format(@"{0}\file_{1}", subfolder.FullName, j), 1);
+                    }
+
+                    CreateTestFile(string.Format(@"{0}\file_{1}", uploadDirectory.FullName, i), 1);
+                }
+
+                scp.Upload(uploadDirectory, "uploaded_dir");
+
+                var downloadDirectory =
+                    Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
+
+                scp.Download("uploaded_dir", downloadDirectory);
+
+                var uploadedFiles = uploadDirectory.GetFiles("*.*", SearchOption.AllDirectories);
+                var downloadFiles = downloadDirectory.GetFiles("*.*", SearchOption.AllDirectories);
+
+                var result = from f1 in uploadedFiles
+                             from f2 in downloadFiles
+                             where
+                                 f1.FullName.Substring(uploadDirectory.FullName.Length) ==
+                                 f2.FullName.Substring(downloadDirectory.FullName.Length)
+                                 && CalculateMD5(f1.FullName) == CalculateMD5(f2.FullName)
+                             select f1;
+
+                var counter = result.Count();
+
+                scp.Disconnect();
+
+                Assert.IsTrue(counter == uploadedFiles.Length && uploadedFiles.Length == downloadFiles.Length);
+            }
+            RemoveAllFiles();
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_File_20_Parallel_Upload_Download()
+        {
+            using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                scp.Connect();
+
+                var uploadFilenames = new string[20];
+                for (var i = 0; i < uploadFilenames.Length; i++)
+                {
+                    uploadFilenames[i] = Path.GetTempFileName();
+                    CreateTestFile(uploadFilenames[i], 1);
+                }
+
+                _ = Parallel.ForEach(uploadFilenames,
+                                     filename =>
+                                        {
+                                            scp.Upload(new FileInfo(filename), Path.GetFileName(filename));
+                                        });
+                _ = Parallel.ForEach(uploadFilenames,
+                                     filename =>
+                                        {
+                                            scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename)));
+                                        });
+
+                var result = from file in uploadFilenames
+                             where CalculateMD5(file) == CalculateMD5(string.Format("{0}.down", file))
+                             select file;
+
+                scp.Disconnect();
+
+                Assert.IsTrue(result.Count() == uploadFilenames.Length);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Scp")]
+        public void Test_Scp_File_Upload_Download_Events()
+        {
+            using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                scp.Connect();
+
+                var uploadFilenames = new string[10];
+
+                for (var i = 0; i < uploadFilenames.Length; i++)
+                {
+                    uploadFilenames[i] = Path.GetTempFileName();
+                    CreateTestFile(uploadFilenames[i], 1);
+                }
+
+                var uploadedFiles = uploadFilenames.ToDictionary(Path.GetFileName, (filename) => 0L);
+                var downloadedFiles = uploadFilenames.ToDictionary((filename) => string.Format("{0}.down", Path.GetFileName(filename)), (filename) => 0L);
+
+                scp.Uploading += delegate (object sender, ScpUploadEventArgs e)
+                {
+                    uploadedFiles[e.Filename] = e.Uploaded;
+                };
+
+                scp.Downloading += delegate (object sender, ScpDownloadEventArgs e)
+                {
+                    downloadedFiles[string.Format("{0}.down", e.Filename)] = e.Downloaded;
+                };
+
+                _ = Parallel.ForEach(uploadFilenames,
+                                     filename =>
+                                        {
+                                            scp.Upload(new FileInfo(filename), Path.GetFileName(filename));
+                                        });
+                _ = Parallel.ForEach(uploadFilenames,
+                                     filename =>
+                                        {
+                                            scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename)));
+                                        });
+
+                var result = from uf in uploadedFiles
+                             from df in downloadedFiles
+                             where string.Format("{0}.down", uf.Key) == df.Key && uf.Value == df.Value
+                             select uf;
+
+                scp.Disconnect();
+
+                Assert.IsTrue(result.Count() == uploadFilenames.Length && uploadFilenames.Length == uploadedFiles.Count && uploadedFiles.Count == downloadedFiles.Count);
+            }
+        }
+
+        protected static string CalculateMD5(string fileName)
+        {
+            using (var file = new FileStream(fileName, FileMode.Open))
+            {
+#if NET7_0_OR_GREATER
+                var hash = MD5.HashData(file);
+#else
+#if NET6_0
+                var md5 = MD5.Create();
+#else
+                MD5 md5 = new MD5CryptoServiceProvider();
+#endif // NET6_0
+                var hash = md5.ComputeHash(file);
+#endif // NET7_0_OR_GREATER
+
+                file.Close();
+
+                var sb = new StringBuilder();
+
+                for (var i = 0; i < hash.Length; i++)
+                {
+                    _ = sb.Append(i.ToString("x2"));
+                }
+
+                return sb.ToString();
+            }
+        }
+
+        private void RemoveAllFiles()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                _ = client.RunCommand("rm -rf *");
+                client.Disconnect();
+            }
+        }
+    }
+}

+ 10 - 18
src/Renci.SshNet.Tests/Classes/SftpClientTest.ChangeDirectory.cs → src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ChangeDirectory.cs

@@ -1,21 +1,18 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Renci.SshNet.Common;
-using Renci.SshNet.Tests.Properties;
+using Renci.SshNet.Common;
 
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
 {
 {
     /// <summary>
     /// <summary>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
     {
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         public void Test_Sftp_ChangeDirectory_Root_Dont_Exists()
         public void Test_Sftp_ChangeDirectory_Root_Dont_Exists()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.ChangeDirectory("/asdasd");
                 sftp.ChangeDirectory("/asdasd");
@@ -24,11 +21,10 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         public void Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_Exists()
         public void Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_Exists()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.ChangeDirectory("/asdasd/");
                 sftp.ChangeDirectory("/asdasd/");
@@ -37,11 +33,10 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         public void Test_Sftp_ChangeDirectory_Subfolder_Dont_Exists()
         public void Test_Sftp_ChangeDirectory_Subfolder_Dont_Exists()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.ChangeDirectory("/asdasd/sssddds");
                 sftp.ChangeDirectory("/asdasd/sssddds");
@@ -50,11 +45,10 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         public void Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_Exists()
         public void Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_Exists()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.ChangeDirectory("/asdasd/sssddds/");
                 sftp.ChangeDirectory("/asdasd/sssddds/");
@@ -63,10 +57,9 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_ChangeDirectory_Which_Exists()
         public void Test_Sftp_ChangeDirectory_Which_Exists()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.ChangeDirectory("/usr");
                 sftp.ChangeDirectory("/usr");
@@ -76,10 +69,9 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash()
         public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.ChangeDirectory("/usr/");
                 sftp.ChangeDirectory("/usr/");
@@ -87,4 +79,4 @@ namespace Renci.SshNet.Tests.Classes
             }
             }
         }
         }
     }
     }
-}
+}

+ 72 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.CreateDirectory.cs

@@ -0,0 +1,72 @@
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+    /// </summary>
+    public partial class SftpClientTest : IntegrationTestBase
+    {
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Sftp_CreateDirectory_In_Current_Location()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.CreateDirectory("test-in-current");
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SftpPermissionDeniedException))]
+        public void Test_Sftp_CreateDirectory_In_Forbidden_Directory()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password))
+            {
+                sftp.Connect();
+
+                sftp.CreateDirectory("/sbin/test");
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SftpPathNotFoundException))]
+        public void Test_Sftp_CreateDirectory_Invalid_Path()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.CreateDirectory("/abcdefg/abcefg");
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SshException))]
+        public void Test_Sftp_CreateDirectory_Already_Exists()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.CreateDirectory("test");
+
+                sftp.CreateDirectory("test");
+
+                sftp.Disconnect();
+            }
+        }
+    }
+}

+ 71 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.DeleteDirectory.cs

@@ -0,0 +1,71 @@
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+    /// </summary>
+    public partial class SftpClientTest : IntegrationTestBase
+    {
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SftpPathNotFoundException))]
+        public void Test_Sftp_DeleteDirectory_Which_Doesnt_Exists()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.DeleteDirectory("abcdef");
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SftpPermissionDeniedException))]
+        public void Test_Sftp_DeleteDirectory_Which_No_Permissions()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password))
+            {
+                sftp.Connect();
+
+                sftp.DeleteDirectory("/usr");
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Sftp_DeleteDirectory()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.CreateDirectory("abcdef");
+                sftp.DeleteDirectory("abcdef");
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [Description("Test passing null to DeleteDirectory.")]
+        [ExpectedException(typeof(ArgumentException))]
+        public void Test_Sftp_DeleteDirectory_Null()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.DeleteDirectory(null);
+
+                sftp.Disconnect();
+            }
+        }
+    }
+}

+ 11 - 24
src/Renci.SshNet.Tests/Classes/SftpClientTest.Download.cs → src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Download.cs

@@ -1,26 +1,18 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Renci.SshNet.Common;
-using Renci.SshNet.Tests.Properties;
-using System;
-using System.IO;
+using Renci.SshNet.Common;
 
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
 {
 {
     /// <summary>
     /// <summary>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
     {
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPermissionDeniedException))]
         [ExpectedException(typeof(SftpPermissionDeniedException))]
         public void Test_Sftp_Download_Forbidden()
         public void Test_Sftp_Download_Forbidden()
         {
         {
-            if (Resources.USERNAME == "root")
-                Assert.Fail("Must not run this test as root!");
-
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
@@ -37,11 +29,10 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         public void Test_Sftp_Download_File_Not_Exists()
         public void Test_Sftp_Download_File_Not_Exists()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
@@ -57,12 +48,11 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginDownloadFile")]
         [Description("Test passing null to BeginDownloadFile")]
         [ExpectedException(typeof(ArgumentNullException))]
         [ExpectedException(typeof(ArgumentNullException))]
         public void Test_Sftp_BeginDownloadFile_StreamIsNull()
         public void Test_Sftp_BeginDownloadFile_StreamIsNull()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.BeginDownloadFile("aaaa", null, null, null);
                 sftp.BeginDownloadFile("aaaa", null, null, null);
@@ -72,12 +62,11 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginDownloadFile")]
         [Description("Test passing null to BeginDownloadFile")]
         [ExpectedException(typeof(ArgumentException))]
         [ExpectedException(typeof(ArgumentException))]
         public void Test_Sftp_BeginDownloadFile_FileNameIsWhiteSpace()
         public void Test_Sftp_BeginDownloadFile_FileNameIsWhiteSpace()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.BeginDownloadFile("   ", new MemoryStream(), null, null);
                 sftp.BeginDownloadFile("   ", new MemoryStream(), null, null);
@@ -87,12 +76,11 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginDownloadFile")]
         [Description("Test passing null to BeginDownloadFile")]
         [ExpectedException(typeof(ArgumentException))]
         [ExpectedException(typeof(ArgumentException))]
         public void Test_Sftp_BeginDownloadFile_FileNameIsNull()
         public void Test_Sftp_BeginDownloadFile_FileNameIsNull()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 sftp.BeginDownloadFile(null, new MemoryStream(), null, null);
                 sftp.BeginDownloadFile(null, new MemoryStream(), null, null);
@@ -102,15 +90,14 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(ArgumentException))]
         [ExpectedException(typeof(ArgumentException))]
         public void Test_Sftp_EndDownloadFile_Invalid_Async_Handle()
         public void Test_Sftp_EndDownloadFile_Invalid_Async_Handle()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 var filename = Path.GetTempFileName();
                 var filename = Path.GetTempFileName();
-                this.CreateTestFile(filename, 1);
+                CreateTestFile(filename, 1);
                 sftp.UploadFile(File.OpenRead(filename), "test123");
                 sftp.UploadFile(File.OpenRead(filename), "test123");
                 var async1 = sftp.BeginListDirectory("/", null, null);
                 var async1 = sftp.BeginListDirectory("/", null, null);
                 var async2 = sftp.BeginDownloadFile("test123", new MemoryStream(), null, null);
                 var async2 = sftp.BeginDownloadFile("test123", new MemoryStream(), null, null);
@@ -118,4 +105,4 @@ namespace Renci.SshNet.Tests.Classes
             }
             }
         }
         }
     }
     }
-}
+}

+ 263 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.ListDirectory.cs

@@ -0,0 +1,263 @@
+using System.Diagnostics;
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+    /// </summary>
+    public partial class SftpClientTest : IntegrationTestBase
+    {
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SftpPermissionDeniedException))]
+        public void Test_Sftp_ListDirectory_Permission_Denied()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                var files = sftp.ListDirectory("/root");
+                foreach (var file in files)
+                {
+                    Debug.WriteLine(file.FullName);
+                }
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SftpPathNotFoundException))]
+        public void Test_Sftp_ListDirectory_Not_Exists()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                var files = sftp.ListDirectory("/asdfgh");
+                foreach (var file in files)
+                {
+                    Debug.WriteLine(file.FullName);
+                }
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Sftp_ListDirectory_Current()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                var files = sftp.ListDirectory(".");
+
+                Assert.IsTrue(files.Count() > 0);
+
+                foreach (var file in files)
+                {
+                    Debug.WriteLine(file.FullName);
+                }
+
+                sftp.Disconnect();
+            }
+        }
+
+#if NET6_0_OR_GREATER
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public async Task Test_Sftp_ListDirectoryAsync_Current()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+                var cts = new CancellationTokenSource();
+                cts.CancelAfter(TimeSpan.FromMinutes(1));
+                var count = 0;
+                await foreach(var file in sftp.ListDirectoryAsync(".", cts.Token))
+                {
+                    count++;
+                    Debug.WriteLine(file.FullName);
+                }
+
+                Assert.IsTrue(count > 0);
+
+                sftp.Disconnect();
+            }
+        }
+#endif
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Sftp_ListDirectory_Empty()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                var files = sftp.ListDirectory(string.Empty);
+
+                Assert.IsTrue(files.Count() > 0);
+
+                foreach (var file in files)
+                {
+                    Debug.WriteLine(file.FullName);
+                }
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [Description("Test passing null to ListDirectory.")]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void Test_Sftp_ListDirectory_Null()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                var files = sftp.ListDirectory(null);
+
+                Assert.IsTrue(files.Count() > 0);
+
+                foreach (var file in files)
+                {
+                    Debug.WriteLine(file.FullName);
+                }
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Sftp_ListDirectory_HugeDirectory()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                //  Create 10000 directory items
+                for (int i = 0; i < 10000; i++)
+                {
+                    sftp.CreateDirectory(string.Format("test_{0}", i));
+                }
+
+                var files = sftp.ListDirectory(".");
+
+                //  Ensure that directory has at least 10000 items
+                Assert.IsTrue(files.Count() > 10000);
+
+                sftp.Disconnect();
+            }
+
+            RemoveAllFiles();
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Sftp_Change_Directory()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet");
+
+                sftp.CreateDirectory("test1");
+
+                sftp.ChangeDirectory("test1");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1");
+
+                sftp.CreateDirectory("test1_1");
+                sftp.CreateDirectory("test1_2");
+                sftp.CreateDirectory("test1_3");
+
+                var files = sftp.ListDirectory(".");
+
+                Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}", sftp.WorkingDirectory)));
+
+                sftp.ChangeDirectory("test1_1");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
+
+                sftp.ChangeDirectory("../test1_2");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2");
+
+                sftp.ChangeDirectory("..");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1");
+
+                sftp.ChangeDirectory("..");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet");
+
+                files = sftp.ListDirectory("test1/test1_1");
+
+                Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}/test1/test1_1", sftp.WorkingDirectory)));
+
+                sftp.ChangeDirectory("test1/test1_1");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
+
+                sftp.ChangeDirectory("/home/sshnet/test1/test1_1");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
+
+                sftp.ChangeDirectory("/home/sshnet/test1/test1_1/../test1_2");
+
+                Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2");
+
+                sftp.ChangeDirectory("../../");
+
+                sftp.DeleteDirectory("test1/test1_1");
+                sftp.DeleteDirectory("test1/test1_2");
+                sftp.DeleteDirectory("test1/test1_3");
+                sftp.DeleteDirectory("test1");
+
+                sftp.Disconnect();
+            }
+
+            RemoveAllFiles();
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [Description("Test passing null to ChangeDirectory.")]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void Test_Sftp_ChangeDirectory_Null()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.ChangeDirectory(null);
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [Description("Test calling EndListDirectory method more then once.")]
+        [ExpectedException(typeof(ArgumentException))]
+        public void Test_Sftp_Call_EndListDirectory_Twice()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+                var ar = sftp.BeginListDirectory("/", null, null);
+                var result = sftp.EndListDirectory(ar);
+                var result1 = sftp.EndListDirectory(ar);
+            }
+        }
+    }
+}

+ 6 - 13
src/Renci.SshNet.Tests/Classes/SftpClientTest.RenameFile.cs → src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFile.cs

@@ -1,21 +1,15 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Renci.SshNet.Tests.Properties;
-using System;
-using System.IO;
-
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
 {
 {
     /// <summary>
     /// <summary>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
     {
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_Rename_File()
         public void Test_Sftp_Rename_File()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
@@ -23,7 +17,7 @@ namespace Renci.SshNet.Tests.Classes
                 string remoteFileName1 = Path.GetRandomFileName();
                 string remoteFileName1 = Path.GetRandomFileName();
                 string remoteFileName2 = Path.GetRandomFileName();
                 string remoteFileName2 = Path.GetRandomFileName();
 
 
-                this.CreateTestFile(uploadedFileName, 1);
+                CreateTestFile(uploadedFileName, 1);
 
 
                 using (var file = File.OpenRead(uploadedFileName))
                 using (var file = File.OpenRead(uploadedFileName))
                 {
                 {
@@ -42,12 +36,11 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to RenameFile.")]
         [Description("Test passing null to RenameFile.")]
         [ExpectedException(typeof(ArgumentNullException))]
         [ExpectedException(typeof(ArgumentNullException))]
         public void Test_Sftp_RenameFile_Null()
         public void Test_Sftp_RenameFile_Null()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
@@ -57,4 +50,4 @@ namespace Renci.SshNet.Tests.Classes
             }
             }
         }
         }
     }
     }
-}
+}

+ 56 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.RenameFileAsync.cs

@@ -0,0 +1,56 @@
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+    /// </summary>
+    public partial class SftpClientTest : IntegrationTestBase
+    {
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public async Task Test_Sftp_RenameFileAsync()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                await sftp.ConnectAsync(default);
+
+                string uploadedFileName = Path.GetTempFileName();
+                string remoteFileName1 = Path.GetRandomFileName();
+                string remoteFileName2 = Path.GetRandomFileName();
+
+                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")]
+        [Description("Test passing null to RenameFile.")]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public async Task Test_Sftp_RenameFileAsync_Null()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                await sftp.ConnectAsync(default);
+
+                await sftp.RenameFileAsync(null, null, default);
+
+                sftp.Disconnect();
+            }
+        }
+    }
+}

+ 8 - 14
src/Renci.SshNet.Tests/Classes/SftpClientTest.SynchronizeDirectories.cs → src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.SynchronizeDirectories.cs

@@ -1,31 +1,26 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Renci.SshNet.Tests.Properties;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
+using System.Diagnostics;
 
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
 {
 {
     /// <summary>
     /// <summary>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
     {
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_SynchronizeDirectories()
         public void Test_Sftp_SynchronizeDirectories()
         {
         {
             RemoveAllFiles();
             RemoveAllFiles();
 
 
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
                 string uploadedFileName = Path.GetTempFileName();
                 string uploadedFileName = Path.GetTempFileName();
 
 
                 string sourceDir = Path.GetDirectoryName(uploadedFileName);
                 string sourceDir = Path.GetDirectoryName(uploadedFileName);
-                string destDir = "/";
+                string destDir = "/home/sshnet/";
                 string searchPattern = Path.GetFileName(uploadedFileName);
                 string searchPattern = Path.GetFileName(uploadedFileName);
                 var upLoadedFiles = sftp.SynchronizeDirectories(sourceDir, destDir, searchPattern);
                 var upLoadedFiles = sftp.SynchronizeDirectories(sourceDir, destDir, searchPattern);
 
 
@@ -42,19 +37,18 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_BeginSynchronizeDirectories()
         public void Test_Sftp_BeginSynchronizeDirectories()
         {
         {
             RemoveAllFiles();
             RemoveAllFiles();
 
 
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
                 string uploadedFileName = Path.GetTempFileName();
                 string uploadedFileName = Path.GetTempFileName();
 
 
                 string sourceDir = Path.GetDirectoryName(uploadedFileName);
                 string sourceDir = Path.GetDirectoryName(uploadedFileName);
-                string destDir = "/";
+                string destDir = "/home/sshnet/";
                 string searchPattern = Path.GetFileName(uploadedFileName);
                 string searchPattern = Path.GetFileName(uploadedFileName);
 
 
                 var asyncResult = sftp.BeginSynchronizeDirectories(sourceDir,
                 var asyncResult = sftp.BeginSynchronizeDirectories(sourceDir,
@@ -83,4 +77,4 @@ namespace Renci.SshNet.Tests.Classes
             }
             }
         }
         }
     }
     }
-}
+}

+ 67 - 65
src/Renci.SshNet.Tests/Classes/SftpClientTest.Upload.cs → src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.Upload.cs

@@ -1,34 +1,27 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Renci.SshNet.Common;
+using Renci.SshNet.Common;
 using Renci.SshNet.Sftp;
 using Renci.SshNet.Sftp;
-using Renci.SshNet.Tests.Properties;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
 
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
 {
 {
     /// <summary>
     /// <summary>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
     {
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_Upload_And_Download_1MB_File()
         public void Test_Sftp_Upload_And_Download_1MB_File()
         {
         {
             RemoveAllFiles();
             RemoveAllFiles();
 
 
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
-                string uploadedFileName = Path.GetTempFileName();
-                string remoteFileName = Path.GetRandomFileName();
+                var uploadedFileName = Path.GetTempFileName();
+                var remoteFileName = Path.GetRandomFileName();
 
 
-                this.CreateTestFile(uploadedFileName, 1);
+                CreateTestFile(uploadedFileName, 1);
 
 
                 //  Calculate has value
                 //  Calculate has value
                 var uploadedHash = CalculateMD5(uploadedFileName);
                 var uploadedHash = CalculateMD5(uploadedFileName);
@@ -38,7 +31,7 @@ namespace Renci.SshNet.Tests.Classes
                     sftp.UploadFile(file, remoteFileName);
                     sftp.UploadFile(file, remoteFileName);
                 }
                 }
 
 
-                string downloadedFileName = Path.GetTempFileName();
+                var downloadedFileName = Path.GetTempFileName();
 
 
                 using (var file = File.OpenWrite(downloadedFileName))
                 using (var file = File.OpenWrite(downloadedFileName))
                 {
                 {
@@ -60,18 +53,17 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPermissionDeniedException))]
         [ExpectedException(typeof(SftpPermissionDeniedException))]
         public void Test_Sftp_Upload_Forbidden()
         public void Test_Sftp_Upload_Forbidden()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
-                string uploadedFileName = Path.GetTempFileName();
-                string remoteFileName = "/root/1";
+                var uploadedFileName = Path.GetTempFileName();
+                var remoteFileName = "/root/1";
 
 
-                this.CreateTestFile(uploadedFileName, 1);
+                CreateTestFile(uploadedFileName, 1);
 
 
                 using (var file = File.OpenRead(uploadedFileName))
                 using (var file = File.OpenRead(uploadedFileName))
                 {
                 {
@@ -84,7 +76,6 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each()
         public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each()
         {
         {
             var maxFiles = 10;
             var maxFiles = 10;
@@ -92,20 +83,23 @@ namespace Renci.SshNet.Tests.Classes
 
 
             RemoveAllFiles();
             RemoveAllFiles();
 
 
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
+                sftp.OperationTimeout = TimeSpan.FromMinutes(1);
                 sftp.Connect();
                 sftp.Connect();
 
 
                 var testInfoList = new Dictionary<string, TestInfo>();
                 var testInfoList = new Dictionary<string, TestInfo>();
 
 
-                for (int i = 0; i < maxFiles; i++)
+                for (var i = 0; i < maxFiles; i++)
                 {
                 {
-                    var testInfo = new TestInfo();
-                    testInfo.UploadedFileName = Path.GetTempFileName();
-                    testInfo.DownloadedFileName = Path.GetTempFileName();
-                    testInfo.RemoteFileName = Path.GetRandomFileName();
+                    var testInfo = new TestInfo
+                        {
+                            UploadedFileName = Path.GetTempFileName(),
+                            DownloadedFileName = Path.GetTempFileName(),
+                            RemoteFileName = Path.GetRandomFileName()
+                        };
 
 
-                    this.CreateTestFile(testInfo.UploadedFileName, maxSize);
+                    CreateTestFile(testInfo.UploadedFileName, maxSize);
 
 
                     //  Calculate hash value
                     //  Calculate hash value
                     testInfo.UploadedHash = CalculateMD5(testInfo.UploadedFileName);
                     testInfo.UploadedHash = CalculateMD5(testInfo.UploadedFileName);
@@ -130,7 +124,7 @@ namespace Renci.SshNet.Tests.Classes
                 }
                 }
 
 
                 //  Wait for upload to finish
                 //  Wait for upload to finish
-                bool uploadCompleted = false;
+                var uploadCompleted = false;
                 while (!uploadCompleted)
                 while (!uploadCompleted)
                 {
                 {
                     //  Assume upload completed
                     //  Assume upload completed
@@ -174,7 +168,7 @@ namespace Renci.SshNet.Tests.Classes
                 }
                 }
 
 
                 //  Wait for download to finish
                 //  Wait for download to finish
-                bool downloadCompleted = false;
+                var downloadCompleted = false;
                 while (!downloadCompleted)
                 while (!downloadCompleted)
                 {
                 {
                     //  Assume download completed
                     //  Assume download completed
@@ -206,6 +200,11 @@ namespace Renci.SshNet.Tests.Classes
 
 
                     testInfo.DownloadedHash = CalculateMD5(testInfo.DownloadedFileName);
                     testInfo.DownloadedHash = CalculateMD5(testInfo.DownloadedFileName);
 
 
+                    Console.WriteLine(remoteFile);
+                    Console.WriteLine("UploadedBytes: "+ testInfo.UploadResult.UploadedBytes);
+                    Console.WriteLine("DownloadedBytes: " + testInfo.DownloadResult.DownloadedBytes);
+                    Console.WriteLine("UploadedHash: " + testInfo.UploadedHash);
+                    Console.WriteLine("DownloadedHash: " + testInfo.DownloadedHash);
                     if (!(testInfo.UploadResult.UploadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes == testInfo.UploadResult.UploadedBytes))
                     if (!(testInfo.UploadResult.UploadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes == testInfo.UploadResult.UploadedBytes))
                     {
                     {
                         uploadDownloadSizeOk = false;
                         uploadDownloadSizeOk = false;
@@ -238,21 +237,20 @@ namespace Renci.SshNet.Tests.Classes
         //  TODO:   Split this test into multiple tests
         //  TODO:   Split this test into multiple tests
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test that delegates passed to BeginUploadFile, BeginDownloadFile and BeginListDirectory are actually called.")]
         [Description("Test that delegates passed to BeginUploadFile, BeginDownloadFile and BeginListDirectory are actually called.")]
         public void Test_Sftp_Ensure_Async_Delegates_Called_For_BeginFileUpload_BeginFileDownload_BeginListDirectory()
         public void Test_Sftp_Ensure_Async_Delegates_Called_For_BeginFileUpload_BeginFileDownload_BeginListDirectory()
         {
         {
             RemoveAllFiles();
             RemoveAllFiles();
 
 
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
 
 
-                string remoteFileName = Path.GetRandomFileName();
-                string localFileName = Path.GetRandomFileName();
-                bool uploadDelegateCalled = false;
-                bool downloadDelegateCalled = false;
-                bool listDirectoryDelegateCalled = false;
+                var remoteFileName = Path.GetRandomFileName();
+                var localFileName = Path.GetRandomFileName();
+                var uploadDelegateCalled = false;
+                var downloadDelegateCalled = false;
+                var listDirectoryDelegateCalled = false;
                 IAsyncResult asyncResult;
                 IAsyncResult asyncResult;
 
 
                 // Test for BeginUploadFile.
                 // Test for BeginUploadFile.
@@ -261,11 +259,14 @@ namespace Renci.SshNet.Tests.Classes
 
 
                 using (var fileStream = File.OpenRead(localFileName))
                 using (var fileStream = File.OpenRead(localFileName))
                 {
                 {
-                    asyncResult = sftp.BeginUploadFile(fileStream, remoteFileName, delegate(IAsyncResult ar)
-                    {
-                        sftp.EndUploadFile(ar);
-                        uploadDelegateCalled = true;
-                    }, null);
+                    asyncResult = sftp.BeginUploadFile(fileStream,
+                                                       remoteFileName,
+                                                       delegate(IAsyncResult ar)
+                                                           {
+                                                               sftp.EndUploadFile(ar);
+                                                               uploadDelegateCalled = true;
+                                                           },
+                                                       null);
 
 
                     while (!asyncResult.IsCompleted)
                     while (!asyncResult.IsCompleted)
                     {
                     {
@@ -282,11 +283,14 @@ namespace Renci.SshNet.Tests.Classes
                 asyncResult = null;
                 asyncResult = null;
                 using (var fileStream = File.OpenWrite(localFileName))
                 using (var fileStream = File.OpenWrite(localFileName))
                 {
                 {
-                    asyncResult = sftp.BeginDownloadFile(remoteFileName, fileStream, delegate(IAsyncResult ar)
-                    {
-                        sftp.EndDownloadFile(ar);
-                        downloadDelegateCalled = true;
-                    }, null);
+                    asyncResult = sftp.BeginDownloadFile(remoteFileName,
+                                                         fileStream,
+                                                         delegate(IAsyncResult ar)
+                                                            {
+                                                                sftp.EndDownloadFile(ar);
+                                                                downloadDelegateCalled = true;
+                                                            },
+                                                         null);
 
 
                     while (!asyncResult.IsCompleted)
                     while (!asyncResult.IsCompleted)
                     {
                     {
@@ -301,11 +305,13 @@ namespace Renci.SshNet.Tests.Classes
                 // Test for BeginListDirectory.
                 // Test for BeginListDirectory.
 
 
                 asyncResult = null;
                 asyncResult = null;
-                asyncResult = sftp.BeginListDirectory(sftp.WorkingDirectory, delegate(IAsyncResult ar)
-                {
-                    sftp.EndListDirectory(ar);
-                    listDirectoryDelegateCalled = true;
-                }, null);
+                asyncResult = sftp.BeginListDirectory(sftp.WorkingDirectory,
+                                                      delegate(IAsyncResult ar)
+                                                        {
+                                                            _ = sftp.EndListDirectory(ar);
+                                                            listDirectoryDelegateCalled = true;
+                                                        },
+                                                      null);
 
 
                 while (!asyncResult.IsCompleted)
                 while (!asyncResult.IsCompleted)
                 {
                 {
@@ -318,64 +324,60 @@ namespace Renci.SshNet.Tests.Classes
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginUploadFile")]
         [Description("Test passing null to BeginUploadFile")]
         [ExpectedException(typeof(ArgumentNullException))]
         [ExpectedException(typeof(ArgumentNullException))]
         public void Test_Sftp_BeginUploadFile_StreamIsNull()
         public void Test_Sftp_BeginUploadFile_StreamIsNull()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
-                sftp.BeginUploadFile(null, "aaaaa", null, null);
+                _ = sftp.BeginUploadFile(null, "aaaaa", null, null);
                 sftp.Disconnect();
                 sftp.Disconnect();
             }
             }
         }
         }
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginUploadFile")]
         [Description("Test passing null to BeginUploadFile")]
         [ExpectedException(typeof(ArgumentException))]
         [ExpectedException(typeof(ArgumentException))]
         public void Test_Sftp_BeginUploadFile_FileNameIsWhiteSpace()
         public void Test_Sftp_BeginUploadFile_FileNameIsWhiteSpace()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
-                sftp.BeginUploadFile(new MemoryStream(), "   ", null, null);
+                _ = sftp.BeginUploadFile(new MemoryStream(), "   ", null, null);
                 sftp.Disconnect();
                 sftp.Disconnect();
             }
             }
         }
         }
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginUploadFile")]
         [Description("Test passing null to BeginUploadFile")]
         [ExpectedException(typeof(ArgumentException))]
         [ExpectedException(typeof(ArgumentException))]
         public void Test_Sftp_BeginUploadFile_FileNameIsNull()
         public void Test_Sftp_BeginUploadFile_FileNameIsNull()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
-                sftp.BeginUploadFile(new MemoryStream(), null, null, null);
+                _ = sftp.BeginUploadFile(new MemoryStream(), null, null, null);
                 sftp.Disconnect();
                 sftp.Disconnect();
             }
             }
         }
         }
 
 
         [TestMethod]
         [TestMethod]
         [TestCategory("Sftp")]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(ArgumentException))]
         [ExpectedException(typeof(ArgumentException))]
         public void Test_Sftp_EndUploadFile_Invalid_Async_Handle()
         public void Test_Sftp_EndUploadFile_Invalid_Async_Handle()
         {
         {
-            using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
             {
             {
                 sftp.Connect();
                 sftp.Connect();
                 var async1 = sftp.BeginListDirectory("/", null, null);
                 var async1 = sftp.BeginListDirectory("/", null, null);
                 var filename = Path.GetTempFileName();
                 var filename = Path.GetTempFileName();
-                this.CreateTestFile(filename, 100);
+                CreateTestFile(filename, 100);
                 var async2 = sftp.BeginUploadFile(File.OpenRead(filename), "test", null, null);
                 var async2 = sftp.BeginUploadFile(File.OpenRead(filename), "test", null, null);
                 sftp.EndUploadFile(async1);
                 sftp.EndUploadFile(async1);
             }
             }
         }
         }
     }
     }
-}
+}

+ 68 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpClientTest.cs

@@ -0,0 +1,68 @@
+using System.Security.Cryptography;
+
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
+    /// </summary>
+    [TestClass]
+    public partial class SftpClientTest
+    {
+        protected static string CalculateMD5(string fileName)
+        {
+            using (FileStream file = new FileStream(fileName, FileMode.Open))
+            {
+                var hash = MD5.HashData(file);
+
+                file.Close();
+
+                StringBuilder sb = new StringBuilder();
+                for (var i = 0; i < hash.Length; i++)
+                {
+                    sb.Append(hash[i].ToString("x2"));
+                }
+                return sb.ToString();
+            }
+        }
+
+        private void RemoveAllFiles()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                client.RunCommand("rm -rf *");
+                client.Disconnect();
+            }
+        }
+
+        /// <summary>
+        /// Helper class to help with upload and download testing
+        /// </summary>
+        private class TestInfo
+        {
+            public string RemoteFileName { get; set; }
+
+            public string UploadedFileName { get; set; }
+
+            public string DownloadedFileName { get; set; }
+
+            //public ulong UploadedBytes { get; set; }
+
+            //public ulong DownloadedBytes { get; set; }
+
+            public FileStream UploadedFile { get; set; }
+
+            public FileStream DownloadedFile { get; set; }
+
+            public string UploadedHash { get; set; }
+
+            public string DownloadedHash { get; set; }
+
+            public SftpUploadAsyncResult UploadResult { get; set; }
+
+            public SftpDownloadAsyncResult DownloadResult { get; set; }
+        }
+    }
+}

+ 120 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SftpFileTest.cs

@@ -0,0 +1,120 @@
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Represents SFTP file information
+    /// </summary>
+    [TestClass]
+    public class SftpFileTest : IntegrationTestBase
+    {
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Get_Root_Directory()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+                var directory = sftp.Get("/");
+
+                Assert.AreEqual("/", directory.FullName);
+                Assert.IsTrue(directory.IsDirectory);
+                Assert.IsFalse(directory.IsRegularFile);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [ExpectedException(typeof(SftpPathNotFoundException))]
+        public void Test_Get_Invalid_Directory()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.Get("/xyz");
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Get_File()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.UploadFile(new MemoryStream(), "abc.txt");
+
+                var file = sftp.Get("abc.txt");
+
+                Assert.AreEqual("/home/sshnet/abc.txt", file.FullName);
+                Assert.IsTrue(file.IsRegularFile);
+                Assert.IsFalse(file.IsDirectory);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        [Description("Test passing null to Get.")]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void Test_Get_File_Null()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                var file = sftp.Get(null);
+
+                sftp.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Get_International_File()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                sftp.UploadFile(new MemoryStream(), "test-üöä-");
+
+                var file = sftp.Get("test-üöä-");
+
+                Assert.AreEqual("/home/sshnet/test-üöä-", file.FullName);
+                Assert.IsTrue(file.IsRegularFile);
+                Assert.IsFalse(file.IsDirectory);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("Sftp")]
+        public void Test_Sftp_SftpFile_MoveTo()
+        {
+            using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                sftp.Connect();
+
+                string uploadedFileName = Path.GetTempFileName();
+                string remoteFileName = Path.GetRandomFileName();
+                string newFileName = Path.GetRandomFileName();
+
+                CreateTestFile(uploadedFileName, 1);
+
+                using (var file = File.OpenRead(uploadedFileName))
+                {
+                    sftp.UploadFile(file, remoteFileName);
+                }
+
+                var sftpFile = sftp.Get(remoteFileName);
+
+                sftpFile.MoveTo(newFileName);
+
+                Assert.AreEqual(newFileName, sftpFile.Name);
+
+                sftp.Disconnect();
+            }
+        }
+    }
+}

+ 545 - 0
src/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs

@@ -0,0 +1,545 @@
+using System.Diagnostics;
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
+{
+    /// <summary>
+    /// Represents SSH command that can be executed.
+    /// </summary>
+    [TestClass]
+    public class SshCommandTest : IntegrationTestBase
+    {
+        [TestMethod]
+        public void Test_Run_SingleCommand()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand RunCommand Result
+                client.Connect();
+
+                var testValue = Guid.NewGuid().ToString();
+                var command = client.RunCommand(string.Format("echo {0}", testValue));
+                var result = command.Result;
+                result = result.Substring(0, result.Length - 1);    //  Remove \n character returned by command
+
+                client.Disconnect();
+                #endregion
+
+                Assert.IsTrue(result.Equals(testValue));
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_SingleCommand()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand CreateCommand Execute
+                client.Connect();
+
+                var testValue = Guid.NewGuid().ToString();
+                var command = string.Format("echo {0}", testValue);
+                var cmd = client.CreateCommand(command);
+                var result = cmd.Execute();
+                result = result.Substring(0, result.Length - 1);    //  Remove \n character returned by command
+
+                client.Disconnect();
+                #endregion
+
+                Assert.IsTrue(result.Equals(testValue));
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_OutputStream()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand CreateCommand Execute OutputStream
+                client.Connect();
+
+                var cmd = client.CreateCommand("ls -l");   //  very long list
+                var asynch = cmd.BeginExecute();
+
+                var reader = new StreamReader(cmd.OutputStream);
+
+                while (!asynch.IsCompleted)
+                {
+                    var result = reader.ReadToEnd();
+                    if (string.IsNullOrEmpty(result))
+                    {
+                        continue;
+                    }
+
+                    Console.Write(result);
+                }
+
+                _ = cmd.EndExecute(asynch);
+
+                client.Disconnect();
+                #endregion
+
+                Assert.Inconclusive();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_ExtendedOutputStream()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand CreateCommand Execute ExtendedOutputStream
+
+                client.Connect();
+                var cmd = client.CreateCommand("echo 12345; echo 654321 >&2");
+                var result = cmd.Execute();
+
+                Console.Write(result);
+
+                var reader = new StreamReader(cmd.ExtendedOutputStream);
+                Console.WriteLine("DEBUG:");
+                Console.Write(reader.ReadToEnd());
+
+                client.Disconnect();
+
+                #endregion
+
+                Assert.Inconclusive();
+            }
+        }
+
+        [TestMethod]
+        [ExpectedException(typeof(SshOperationTimeoutException))]
+        public void Test_Execute_Timeout()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand CreateCommand Execute CommandTimeout
+                client.Connect();
+                var cmd = client.CreateCommand("sleep 10s");
+                cmd.CommandTimeout = TimeSpan.FromSeconds(5);
+                cmd.Execute();
+                client.Disconnect();
+                #endregion
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Infinite_Timeout()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var cmd = client.CreateCommand("sleep 10s");
+                cmd.Execute();
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_InvalidCommand()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+
+                var cmd = client.CreateCommand(";");
+                cmd.Execute();
+                if (string.IsNullOrEmpty(cmd.Error))
+                {
+                    Assert.Fail("Operation should fail");
+                }
+                Assert.IsTrue(cmd.ExitStatus > 0);
+
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_InvalidCommand_Then_Execute_ValidCommand()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var cmd = client.CreateCommand(";");
+                cmd.Execute();
+                if (string.IsNullOrEmpty(cmd.Error))
+                {
+                    Assert.Fail("Operation should fail");
+                }
+                Assert.IsTrue(cmd.ExitStatus > 0);
+
+                var result = ExecuteTestCommand(client);
+
+                client.Disconnect();
+
+                Assert.IsTrue(result);
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Command_with_ExtendedOutput()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var cmd = client.CreateCommand("echo 12345; echo 654321 >&2");
+                cmd.Execute();
+
+                //var extendedData = Encoding.ASCII.GetString(cmd.ExtendedOutputStream.ToArray());
+                var extendedData = new StreamReader(cmd.ExtendedOutputStream, Encoding.ASCII).ReadToEnd();
+                client.Disconnect();
+
+                Assert.AreEqual("12345\n", cmd.Result);
+                Assert.AreEqual("654321\n", extendedData);
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Command_Reconnect_Execute_Command()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var result = ExecuteTestCommand(client);
+                Assert.IsTrue(result);
+
+                client.Disconnect();
+                client.Connect();
+                result = ExecuteTestCommand(client);
+                Assert.IsTrue(result);
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Command_ExitStatus()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand RunCommand ExitStatus
+                client.Connect();
+
+                var cmd = client.RunCommand("exit 128");
+                
+                Console.WriteLine(cmd.ExitStatus);
+
+                client.Disconnect();
+                #endregion
+
+                Assert.IsTrue(cmd.ExitStatus == 128);
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Command_Asynchronously()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+
+                var cmd = client.CreateCommand("sleep 5s; echo 'test'");
+                var asyncResult = cmd.BeginExecute(null, null);
+                while (!asyncResult.IsCompleted)
+                {
+                    Thread.Sleep(100);
+                }
+
+                cmd.EndExecute(asyncResult);
+
+                Assert.IsTrue(cmd.Result == "test\n");
+
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Command_Asynchronously_With_Error()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+
+                var cmd = client.CreateCommand("sleep 5s; ;");
+                var asyncResult = cmd.BeginExecute(null, null);
+                while (!asyncResult.IsCompleted)
+                {
+                    Thread.Sleep(100);
+                }
+
+                cmd.EndExecute(asyncResult);
+
+                Assert.IsFalse(string.IsNullOrEmpty(cmd.Error));
+
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Command_Asynchronously_With_Callback()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+
+                var callbackCalled = false;
+
+                var cmd = client.CreateCommand("sleep 5s; echo 'test'");
+                var asyncResult = cmd.BeginExecute(new AsyncCallback((s) =>
+                {
+                    callbackCalled = true;
+                }), null);
+                while (!asyncResult.IsCompleted)
+                {
+                    Thread.Sleep(100);
+                }
+
+                cmd.EndExecute(asyncResult);
+
+                Assert.IsTrue(callbackCalled);
+
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Command_Asynchronously_With_Callback_On_Different_Thread()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+
+                var currentThreadId = Thread.CurrentThread.ManagedThreadId;
+                int callbackThreadId = 0;
+
+                var cmd = client.CreateCommand("sleep 5s; echo 'test'");
+                var asyncResult = cmd.BeginExecute(new AsyncCallback((s) =>
+                {
+                    callbackThreadId = Thread.CurrentThread.ManagedThreadId;
+                }), null);
+                while (!asyncResult.IsCompleted)
+                {
+                    Thread.Sleep(100);
+                }
+
+                cmd.EndExecute(asyncResult);
+
+                Assert.AreNotEqual(currentThreadId, callbackThreadId);
+
+                client.Disconnect();
+            }
+        }
+
+        /// <summary>
+        /// Tests for Issue 563.
+        /// </summary>
+        [WorkItem(563), TestMethod]
+        public void Test_Execute_Command_Same_Object_Different_Commands()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var cmd = client.CreateCommand("echo 12345");
+                cmd.Execute();
+                Assert.AreEqual("12345\n", cmd.Result);
+                cmd.Execute("echo 23456");
+                Assert.AreEqual("23456\n", cmd.Result);
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Get_Result_Without_Execution()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var cmd = client.CreateCommand("ls -l");
+
+                Assert.IsTrue(string.IsNullOrEmpty(cmd.Result));
+                client.Disconnect();
+            }
+        }
+
+        [TestMethod]
+        public void Test_Get_Error_Without_Execution()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var cmd = client.CreateCommand("ls -l");
+
+                Assert.IsTrue(string.IsNullOrEmpty(cmd.Error));
+                client.Disconnect();
+            }
+        }
+
+        [WorkItem(703), TestMethod]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void Test_EndExecute_Before_BeginExecute()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                var cmd = client.CreateCommand("ls -l");
+                cmd.EndExecute(null);
+                client.Disconnect();
+            }
+        }
+
+        /// <summary>
+        ///A test for BeginExecute
+        ///</summary>
+        [TestMethod()]
+        public void BeginExecuteTest()
+        {
+            string expected = "123\n";
+            string result;
+
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand CreateCommand BeginExecute IsCompleted EndExecute
+
+                client.Connect();
+
+                var cmd = client.CreateCommand("sleep 15s;echo 123"); // Perform long running task
+
+                var asynch = cmd.BeginExecute();
+
+                while (!asynch.IsCompleted)
+                {
+                    //  Waiting for command to complete...
+                    Thread.Sleep(2000);
+                }
+                result = cmd.EndExecute(asynch);
+                client.Disconnect();
+
+                #endregion
+
+                Assert.IsNotNull(asynch);
+                Assert.AreEqual(expected, result);
+            }
+        }
+
+        [TestMethod]
+        public void Test_Execute_Invalid_Command()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                #region Example SshCommand CreateCommand Error
+
+                client.Connect();
+
+                var cmd = client.CreateCommand(";");
+                cmd.Execute();
+                if (!string.IsNullOrEmpty(cmd.Error))
+                {
+                    Console.WriteLine(cmd.Error);
+                }
+
+                client.Disconnect();
+
+                #endregion
+
+                Assert.Inconclusive();
+            }
+        }
+
+        [TestMethod]
+        public void Test_MultipleThread_Example_MultipleConnections()
+        {
+            try
+            {
+#region Example SshCommand RunCommand Parallel
+                Parallel.For(0, 100,
+                    () =>
+                    {
+                        var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
+                        client.Connect();
+                        return client;
+                    },
+                    (int counter, ParallelLoopState pls, SshClient client) =>
+                    {
+                        var result = client.RunCommand("echo 123");
+                        Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter));
+                        return client;
+                    },
+                    (SshClient client) =>
+                    {
+                        client.Disconnect();
+                        client.Dispose();
+                    }
+                );
+#endregion
+
+            }
+            catch (Exception exp)
+            {
+                Assert.Fail(exp.ToString());
+            }
+        }
+
+        [TestMethod]
+        
+        public void Test_MultipleThread_100_MultipleConnections()
+        {
+            try
+            {
+                Parallel.For(0, 100,
+                    () =>
+                    {
+                        var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
+                        client.Connect();
+                        return client;
+                    },
+                    (int counter, ParallelLoopState pls, SshClient client) =>
+                    {
+                        var result = ExecuteTestCommand(client);
+                        Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter));
+                        Assert.IsTrue(result);
+                        return client;
+                    },
+                    (SshClient client) =>
+                    {
+                        client.Disconnect();
+                        client.Dispose();
+                    }
+                );
+            }
+            catch (Exception exp)
+            {
+                Assert.Fail(exp.ToString());
+            }
+        }
+
+        [TestMethod]
+        public void Test_MultipleThread_100_MultipleSessions()
+        {
+            using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
+            {
+                client.Connect();
+                Parallel.For(0, 100,
+                    (counter) =>
+                    {
+                        var result = ExecuteTestCommand(client);
+                        Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter));
+                        Assert.IsTrue(result);
+                    }
+                );
+
+                client.Disconnect();
+            }
+        }
+
+        private static bool ExecuteTestCommand(SshClient s)
+        {
+            var testValue = Guid.NewGuid().ToString();
+            var command = string.Format("echo {0}", testValue);
+            var cmd = s.CreateCommand(command);
+            var result = cmd.Execute();
+            result = result.Substring(0, result.Length - 1);    //  Remove \n character returned by command
+            return result.Equals(testValue);
+        }
+    }
+}

+ 102 - 0
src/Renci.SshNet.IntegrationTests/PrivateKeyAuthenticationTests.cs

@@ -0,0 +1,102 @@
+using Renci.SshNet.IntegrationTests.Common;
+using Renci.SshNet.TestTools.OpenSSH;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class PrivateKeyAuthenticationTests : TestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+        }
+
+        [TestMethod]
+        public void SshDss()
+        {
+            DoTest(PublicKeyAlgorithm.SshDss, "id_dsa");
+        }
+
+        [TestMethod]
+        public void SshRsa()
+        {
+            DoTest(PublicKeyAlgorithm.SshRsa, "id_rsa");
+        }
+
+        [TestMethod]
+        public void SshRsaSha256()
+        {
+            DoTest(PublicKeyAlgorithm.RsaSha2256, "id_rsa");
+        }
+
+        [TestMethod]
+        public void SshRsaSha512()
+        {
+            DoTest(PublicKeyAlgorithm.RsaSha2512, "id_rsa");
+        }
+
+        [TestMethod]
+        public void Ecdsa256()
+        {
+            DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp256, "key_ecdsa_256_openssh");
+        }
+
+        [TestMethod]
+        public void Ecdsa384()
+        {
+            DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp384, "key_ecdsa_384_openssh");
+        }
+
+        [TestMethod]
+        public void Ecdsa521()
+        {
+            DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp521, "key_ecdsa_521_openssh");
+        }
+
+        [TestMethod]
+        public void Ed25519()
+        {
+            DoTest(PublicKeyAlgorithm.SshEd25519, "key_ed25519_openssh");
+        }
+
+        private void DoTest(PublicKeyAlgorithm publicKeyAlgorithm, string keyResource)
+        {
+            _remoteSshdConfig.ClearPublicKeyAcceptedAlgorithms()
+                             .AddPublicKeyAcceptedAlgorithm(publicKeyAlgorithm)
+                             .Update()
+                             .Restart();
+
+            var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod(keyResource));
+
+            using (var client = new SshClient(connectionInfo))
+            {
+                client.Connect();
+            }
+        }
+
+        private PrivateKeyAuthenticationMethod CreatePrivateKeyAuthenticationMethod(string keyResource)
+        {
+            var privateKey = CreatePrivateKeyFromManifestResource("Renci.SshNet.IntegrationTests.resources.client." + keyResource);
+            return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKey);
+        }
+
+        private PrivateKeyFile CreatePrivateKeyFromManifestResource(string resourceName)
+        {
+            using (var stream = GetManifestResourceStream(resourceName))
+            {
+                return new PrivateKeyFile(stream);
+            }
+        }
+    }
+}

+ 11 - 0
src/Renci.SshNet.IntegrationTests/Program.cs

@@ -0,0 +1,11 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    class Program
+    {
+#if NETFRAMEWORK
+        private static void Main()
+        {
+        }
+#endif
+    }
+}

+ 258 - 0
src/Renci.SshNet.IntegrationTests/RemoteSshd.cs

@@ -0,0 +1,258 @@
+using Renci.SshNet.TestTools.OpenSSH;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    internal class RemoteSshd
+    {
+        private readonly IConnectionInfoFactory _connectionInfoFactory;
+
+        public RemoteSshd(IConnectionInfoFactory connectionInfoFactory)
+        {
+            _connectionInfoFactory = connectionInfoFactory;
+        }
+
+        public RemoteSshdConfig OpenConfig()
+        {
+            return new RemoteSshdConfig(this, _connectionInfoFactory);
+        }
+
+        public RemoteSshd Restart()
+        {
+            // Restart SSH daemon
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                // Kill all processes that start with 'sshd' and that run as root
+                var stopCommand = client.CreateCommand("sudo pkill -9 -U 0 sshd.pam");
+                var stopOutput = stopCommand.Execute();
+                if (stopCommand.ExitStatus != 0)
+                {
+                    throw new ApplicationException($"Stopping ssh service failed with exit code {stopCommand.ExitStatus}.\r\n{stopOutput}\r\n{stopCommand.Error}");
+                }
+
+                var resetFailedCommand = client.CreateCommand("sudo /usr/sbin/sshd.pam");
+                var resetFailedOutput = resetFailedCommand.Execute();
+                if (resetFailedCommand.ExitStatus != 0)
+                {
+                    throw new ApplicationException($"Reset failures for ssh service failed with exit code {resetFailedCommand.ExitStatus}.\r\n{resetFailedOutput}\r\n{resetFailedCommand.Error}");
+                }
+            }
+
+            return this;
+        }
+    }
+
+    internal class RemoteSshdConfig
+    {
+        private const string SshdConfigFilePath = "/etc/ssh/sshd_config";
+        private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true);
+
+        private readonly RemoteSshd _remoteSshd;
+        private readonly IConnectionInfoFactory _connectionInfoFactory;
+        private readonly SshdConfig _config;
+
+        public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connectionInfoFactory)
+        {
+            _remoteSshd = remoteSshd;
+            _connectionInfoFactory = connectionInfoFactory;
+
+            using (var client = new ScpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var memoryStream = new MemoryStream())
+                {
+                    client.Download(SshdConfigFilePath, memoryStream);
+
+                    memoryStream.Position = 0;
+                    _config = SshdConfig.LoadFrom(memoryStream, Encoding.UTF8);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Specifies whether challenge-response authentication is allowed.
+        /// </summary>
+        /// <param name="value"><see langword="true"/> to allow challenge-response authentication.</param>
+        /// <returns>
+        /// The current <see cref="RemoteSshdConfig"/> instance.
+        /// </returns>
+        public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value)
+        {
+            _config.ChallengeResponseAuthentication = value;
+            return this;
+        }
+
+        /// <summary>
+        /// Specifies whether to allow keyboard-interactive authentication.
+        /// </summary>
+        /// <param name="value"><see langword="true"/> to allow keyboard-interactive authentication.</param>
+        /// <returns>
+        /// The current <see cref="RemoteSshdConfig"/> instance.
+        /// </returns>
+        public RemoteSshdConfig WithKeyboardInteractiveAuthentication(bool value)
+        {
+            _config.KeyboardInteractiveAuthentication = value;
+            return this;
+        }
+
+        /// <summary>
+        /// Specifies whether <c>sshd</c> should print /etc/motd when a user logs in interactively.
+        /// </summary>
+        /// <param name="value"><see langword="true"/> if <c>sshd</c> should print /etc/motd when a user logs in interactively.</param>
+        /// <returns>
+        /// The current <see cref="RemoteSshdConfig"/> instance.
+        /// </returns>
+        public RemoteSshdConfig PrintMotd(bool? value = true)
+        {
+            _config.PrintMotd = value;
+            return this;
+        }
+
+        /// <summary>
+        /// Specifies whether TCP forwarding is permitted.
+        /// </summary>
+        /// <param name="value"><see langword="true"/> to allow TCP forwarding.</param>
+        /// <returns>
+        /// The current <see cref="RemoteSshdConfig"/> instance.
+        /// </returns>
+        public RemoteSshdConfig AllowTcpForwarding(bool? value = true)
+        {
+            _config.AllowTcpForwarding = value;
+            return this;
+        }
+
+        public RemoteSshdConfig WithAuthenticationMethods(string user, string authenticationMethods)
+        {
+            var sshNetMatch = _config.Matches.FirstOrDefault(m => m.Users.Contains(user));
+            if (sshNetMatch == null)
+            {
+                sshNetMatch = new Match(new[] { user }, new string[0]);
+                _config.Matches.Add(sshNetMatch);
+            }
+
+            sshNetMatch.AuthenticationMethods = authenticationMethods;
+
+            return this;
+        }
+
+        public RemoteSshdConfig ClearCiphers()
+        {
+            _config.Ciphers.Clear();
+            return this;
+        }
+
+        public RemoteSshdConfig AddCipher(Cipher cipher)
+        {
+            _config.Ciphers.Add(cipher);
+            return this;
+        }
+
+        public RemoteSshdConfig ClearKeyExchangeAlgorithms()
+        {
+            _config.KeyExchangeAlgorithms.Clear();
+            return this;
+        }
+
+        public RemoteSshdConfig AddKeyExchangeAlgorithm(KeyExchangeAlgorithm keyExchangeAlgorithm)
+        {
+            _config.KeyExchangeAlgorithms.Add(keyExchangeAlgorithm);
+            return this;
+        }
+
+        public RemoteSshdConfig ClearPublicKeyAcceptedAlgorithms()
+        {
+            _config.PublicKeyAcceptedAlgorithms.Clear();
+            return this;
+        }
+
+        public RemoteSshdConfig AddPublicKeyAcceptedAlgorithm(PublicKeyAlgorithm publicKeyAlgorithm)
+        {
+            _config.PublicKeyAcceptedAlgorithms.Add(publicKeyAlgorithm);
+            return this;
+        }
+
+        public RemoteSshdConfig ClearMessageAuthenticationCodeAlgorithms()
+        {
+            _config.MessageAuthenticationCodeAlgorithms.Clear();
+            return this;
+        }
+
+        public RemoteSshdConfig AddMessageAuthenticationCodeAlgorithm(MessageAuthenticationCodeAlgorithm messageAuthenticationCodeAlgorithm)
+        {
+            _config.MessageAuthenticationCodeAlgorithms.Add(messageAuthenticationCodeAlgorithm);
+            return this;
+        }
+
+        public RemoteSshdConfig ClearHostKeyAlgorithms()
+        {
+            _config.HostKeyAlgorithms.Clear();
+            return this;
+        }
+
+        public RemoteSshdConfig AddHostKeyAlgorithm(HostKeyAlgorithm hostKeyAlgorithm)
+        {
+            _config.HostKeyAlgorithms.Add(hostKeyAlgorithm);
+            return this;
+        }
+
+        public RemoteSshdConfig ClearSubsystems()
+        {
+            _config.Subsystems.Clear();
+            return this;
+        }
+
+        public RemoteSshdConfig AddSubsystem(Subsystem subsystem)
+        {
+            _config.Subsystems.Add(subsystem);
+            return this;
+        }
+
+        public RemoteSshdConfig WithLogLevel(LogLevel logLevel)
+        {
+            _config.LogLevel = logLevel;
+            return this;
+        }
+
+        public RemoteSshdConfig WithUsePAM(bool usePAM)
+        {
+            _config.UsePAM = usePAM;
+            return this;
+        }
+
+        public RemoteSshdConfig ClearHostKeyFiles()
+        {
+            _config.HostKeyFiles.Clear();
+            return this;
+        }
+
+        public RemoteSshdConfig AddHostKeyFile(string hostKeyFile)
+        {
+            _config.HostKeyFiles.Add(hostKeyFile);
+            return this;
+        }
+
+        public RemoteSshd Update()
+        {
+            using (var client = new ScpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var memoryStream = new MemoryStream())
+                using (var sw = new StreamWriter(memoryStream, Utf8NoBom))
+                {
+                    sw.NewLine = "\n";
+                    _config.SaveTo(sw);
+                    sw.Flush();
+
+                    memoryStream.Position = 0;
+
+                    client.Upload(memoryStream, SshdConfigFilePath);
+                }
+            }
+
+            return _remoteSshd;
+        }
+    }
+}

+ 66 - 0
src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj

@@ -0,0 +1,66 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+
+    <IsPackable>false</IsPackable>
+    <IsTestProject>true</IsTestProject>
+    <!--
+          Even though we're not interested in producing XML docs for test projects, we have to enable this in order to have the .NET Compiler
+          Platform analyzers produce the IDE0005 (Remove unnecessary import) diagnostic.
+            
+          To avoid warnings for missing XML docs, we add CS1591 (Missing XML comment for publicly visible type or member) to the NoWarn property.
+
+          We can stop producing XML docs for test projects (and remove the NoWarn for CS1591) once the following issue is fixed:
+          https://github.com/dotnet/roslyn/issues/41640.
+      -->
+    <NoWarn>$(NoWarn);CS1591;SYSLIB0021;SYSLIB1045;SYSLIB0014;IDE0220;IDE0010</NoWarn>
+      
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
+    <DefineConstants>TRACE;FEATURE_MSTEST_DATATEST;FEATURE_SOCKET_EAP;FEATURE_ENCODING_ASCII;FEATURE_THREAD_SLEEP;FEATURE_THREAD_THREADPOOL</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
+    <PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
+    <PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
+    
+    <PackageReference Include="Testcontainers" Version="3.4.0" />
+    <PackageReference Include="System.Management" Version="4.7.0" />
+    <!--
+        Testcontainers has a dependency on SSH.NET which causes build warnings during assembly resolution:      
+        
+            warning MSB3243: No way to resolve conflict between "Renci.SshNet, Version=2023.0.0.0, Culture=neutral
+            , PublicKeyToken=1cee9f8bde3db106" and "Renci.SshNet, Version=2020.0.2.0, Culture=neutral, PublicKeyToken=1cee9f8bde3db
+            106". Choosing "Renci.SshNet, Version=2023.0.0.0, Culture=neutral, PublicKeyToken=1cee9f8bde3db106" arbitrarily. 
+             
+        To fix, we explicitly exclude the SSH.NET nuget package from this project's dependencies.
+    -->
+    <PackageReference Include="SSH.NET" Version="2020.0.2" ExcludeAssets="All" />
+      
+    <PackageReference Include="coverlet.collector" Version="6.0.0">
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+      <PrivateAssets>all</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Renci.SshNet.TestTools.OpenSSH\Renci.SshNet.TestTools.OpenSSH.csproj" />
+    <ProjectReference Include="..\Renci.SshNet\Renci.SshNet.csproj" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="resources\client\id_dsa" />
+    <EmbeddedResource Include="resources\client\id_dsa.ppk" />
+    <EmbeddedResource Include="resources\client\id_noaccess.rsa" />
+    <EmbeddedResource Include="resources\client\id_rsa" />
+    <EmbeddedResource Include="resources\client\id_rsa_with_pass" />
+    <EmbeddedResource Include="resources\client\key_ecdsa_256_openssh" />
+    <EmbeddedResource Include="resources\client\key_ecdsa_384_openssh" />
+    <EmbeddedResource Include="resources\client\key_ecdsa_521_openssh" />
+    <EmbeddedResource Include="resources\client\key_ed25519_openssh" />
+    <EmbeddedResource Include="resources\issue #70.png" />
+  </ItemGroup>
+</Project>

+ 41 - 0
src/Renci.SshNet.IntegrationTests/ScpClientTests.cs

@@ -0,0 +1,41 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    /// <summary>
+    /// The SCP client integration tests
+    /// </summary>
+    [TestClass]
+    public class ScpClientTests : IntegrationTestBase, IDisposable
+    {
+        private readonly ScpClient _scpClient;
+
+        public ScpClientTests()
+        {
+            _scpClient = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
+            _scpClient.Connect();
+        }
+
+        [TestMethod]
+
+        public void Upload_And_Download_FileStream()
+        {
+            var file = $"/tmp/{Guid.NewGuid()}.txt";
+            var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
+        
+            using var uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
+            _scpClient.Upload(uploadStream, file);
+
+            using var downloadStream = new MemoryStream();
+            _scpClient.Download(file, downloadStream);
+
+            var result = Encoding.UTF8.GetString(downloadStream.ToArray());
+
+            Assert.AreEqual(fileContent, result);
+        }
+
+        public void Dispose()
+        {
+            _scpClient.Disconnect();
+            _scpClient.Dispose();
+        }
+    }
+}

+ 2379 - 0
src/Renci.SshNet.IntegrationTests/ScpTests.cs

@@ -0,0 +1,2379 @@
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    // TODO SCP: UPLOAD / DOWNLOAD ZERO LENGTH FILES
+    // TODO SCP: UPLOAD / DOWNLOAD EMPTY DIRECTORY
+    // TODO SCP: UPLOAD DIRECTORY THAT ALREADY EXISTS ON REMOTE HOST
+
+    [TestClass]
+    public class ScpTests : TestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private IRemotePathTransformation _remotePathTransformation;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _remotePathTransformation = RemotePathTransformation.ShellQuote;
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadStreamDirectoryDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_Stream_DirectoryDoesNotExist()
+        {
+            foreach (var data in GetScpDownloadStreamDirectoryDoesNotExistData())
+            {
+                Scp_Download_Stream_DirectoryDoesNotExist((IRemotePathTransformation) data[0],
+                                                           (string) data[1],
+                                                           (string) data[2]);
+            }
+        }
+#endif
+        public void Scp_Download_Stream_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                              string remotePath,
+                                                              string remoteFile)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            // remove complete directory if it's not the home directory of the user
+            // or else remove the remote file
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+
+            try
+            {
+                using (var downloaded = new MemoryStream())
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Download(completeRemotePath, downloaded);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadStreamFileDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_Stream_FileDoesNotExist()
+        {
+            foreach (var data in GetScpDownloadStreamFileDoesNotExistData())
+            {
+                Scp_Download_Stream_FileDoesNotExist((IRemotePathTransformation)data[0],
+                                                      (string)data[1],
+                                                      (string)data[2]);
+            }
+        }
+#endif
+        public void Scp_Download_Stream_FileDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                         string remotePath,
+                                                         string remoteFile)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            // remove complete directory if it's not the home directory of the user
+            // or else remove the remote file
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+
+                    client.CreateDirectory(remotePath);
+                }
+            }
+
+            try
+            {
+                using (var downloaded = new MemoryStream())
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Download(completeRemotePath, downloaded);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadDirectoryInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_DirectoryInfo_DirectoryDoesNotExist()
+        {
+            foreach (var data in GetScpDownloadDirectoryInfoDirectoryDoesNotExistData())
+            {
+                Scp_Download_DirectoryInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0],
+                                                                 (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Download_DirectoryInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                                     string remotePath)
+        {
+            var localDirectory = Path.GetTempFileName();
+            File.Delete(localDirectory);
+            Directory.CreateDirectory(localDirectory);
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Download(remotePath, new DirectoryInfo(localDirectory));
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remotePath}: No such file or directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                Directory.Delete(localDirectory, true);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadDirectoryInfoExistingFileData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_DirectoryInfo_ExistingFile()
+        {
+            foreach (var data in GetScpDownloadDirectoryInfoExistingFileData())
+            {
+                Scp_Download_DirectoryInfo_ExistingFile((IRemotePathTransformation)data[0],
+                                                         (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Download_DirectoryInfo_ExistingFile(IRemotePathTransformation remotePathTransformation,
+                                                            string remotePath)
+        {
+            var content = CreateMemoryStream(100);
+            content.Position = 0;
+
+            var localDirectory = Path.GetTempFileName();
+            File.Delete(localDirectory);
+            Directory.CreateDirectory(localDirectory);
+
+            var localFile = Path.Combine(localDirectory, PosixPath.GetFileName(remotePath));
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+                    client.UploadFile(content, remotePath);
+                }
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Download(remotePath, new DirectoryInfo(localDirectory));
+                }
+
+                Assert.IsTrue(File.Exists(localFile));
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remotePath, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateFileHash(localFile), CreateHash(downloaded));
+                    }
+                }
+            }
+            finally
+            {
+                Directory.Delete(localDirectory, true);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteFile(remotePath);
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadDirectoryInfoExistingDirectoryData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_DirectoryInfo_ExistingDirectory()
+        {
+            foreach (var data in GetScpDownloadDirectoryInfoExistingDirectoryData())
+            {
+                Scp_Download_DirectoryInfo_ExistingDirectory((IRemotePathTransformation)data[0],
+                                                             (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Download_DirectoryInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation,
+                                                                 string remotePath)
+        {
+            var localDirectory = Path.GetTempFileName();
+            File.Delete(localDirectory);
+            Directory.CreateDirectory(localDirectory);
+
+            var localPathFile1 = Path.Combine(localDirectory, "file1 23");
+            var remotePathFile1 = CombinePaths(remotePath, "file1 23");
+            var contentFile1 = CreateMemoryStream(1024);
+            contentFile1.Position = 0;
+
+            var localPathFile2 = Path.Combine(localDirectory, "file2 #$%");
+            var remotePathFile2 = CombinePaths(remotePath, "file2 #$%");
+            var contentFile2 = CreateMemoryStream(2048);
+            contentFile2.Position = 0;
+
+            var localPathSubDirectory = Path.Combine(localDirectory, "subdir $1%#");
+            var remotePathSubDirectory = CombinePaths(remotePath, "subdir $1%#");
+
+            var localPathFile3 = Path.Combine(localPathSubDirectory, "file3 %$#");
+            var remotePathFile3 = CombinePaths(remotePathSubDirectory, "file3 %$#");
+            var contentFile3 = CreateMemoryStream(256);
+            contentFile3.Position = 0;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remotePathFile1))
+                {
+                    client.DeleteFile(remotePathFile1);
+                }
+
+                if (client.Exists(remotePathFile2))
+                {
+                    client.DeleteFile(remotePathFile2);
+                }
+
+                if (client.Exists(remotePathFile3))
+                {
+                    client.DeleteFile(remotePathFile3);
+                }
+
+                if (client.Exists(remotePathSubDirectory))
+                {
+                    client.DeleteDirectory(remotePathSubDirectory);
+                }
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (!client.Exists(remotePath))
+                    {
+                        client.CreateDirectory(remotePath);
+                    }
+
+                    client.UploadFile(contentFile1, remotePathFile1);
+                    client.UploadFile(contentFile1, remotePathFile2);
+
+                    client.CreateDirectory(remotePathSubDirectory);
+                    client.UploadFile(contentFile3, remotePathFile3);
+                }
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Download(remotePath, new DirectoryInfo(localDirectory));
+                }
+
+                var localFiles = Directory.GetFiles(localDirectory);
+                Assert.AreEqual(2, localFiles.Length);
+                Assert.IsTrue(localFiles.Contains(localPathFile1));
+                Assert.IsTrue(localFiles.Contains(localPathFile2));
+
+                var localSubDirecties = Directory.GetDirectories(localDirectory);
+                Assert.AreEqual(1, localSubDirecties.Length);
+                Assert.AreEqual(localPathSubDirectory, localSubDirecties[0]);
+
+                var localFilesSubDirectory = Directory.GetFiles(localPathSubDirectory);
+                Assert.AreEqual(1, localFilesSubDirectory.Length);
+                Assert.AreEqual(localPathFile3, localFilesSubDirectory[0]);
+
+                Assert.AreEqual(0, Directory.GetDirectories(localPathSubDirectory).Length);
+            }
+            finally
+            {
+                Directory.Delete(localDirectory, true);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remotePathFile1))
+                    {
+                        client.DeleteFile(remotePathFile1);
+                    }
+
+                    if (client.Exists(remotePathFile2))
+                    {
+                        client.DeleteFile(remotePathFile2);
+                    }
+
+                    if (client.Exists(remotePathFile3))
+                    {
+                        client.DeleteFile(remotePathFile3);
+                    }
+
+                    if (client.Exists(remotePathSubDirectory))
+                    {
+                        client.DeleteDirectory(remotePathSubDirectory);
+                    }
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadFileInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_FileInfo_DirectoryDoesNotExist()
+        {
+            foreach (var data in GetScpDownloadFileInfoDirectoryDoesNotExistData())
+            {
+                Scp_Download_FileInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0],
+                                                            (string)data[1],
+                                                            (string)data[2]);
+            }
+        }
+#endif
+        public void Scp_Download_FileInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                                string remotePath,
+                                                                string remoteFile)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            // remove complete directory if it's not the home directory of the user
+            // or else remove the remote file
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+
+            var fileInfo = new FileInfo(Path.GetTempFileName());
+
+            try
+            {
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Download(completeRemotePath, fileInfo);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                fileInfo.Delete();
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadFileInfoFileDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_FileInfo_FileDoesNotExist()
+        {
+            foreach (var data in GetScpDownloadFileInfoFileDoesNotExistData())
+            {
+                Scp_Download_FileInfo_FileDoesNotExist((IRemotePathTransformation)data[0],
+                                                        (string)data[1],
+                                                        (string)data[2]);
+            }
+        }
+#endif
+        public void Scp_Download_FileInfo_FileDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                           string remotePath,
+                                                           string remoteFile)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            // remove complete directory if it's not the home directory of the user
+            // or else remove the remote file
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+
+                    client.CreateDirectory(remotePath);
+                }
+            }
+
+            var fileInfo = new FileInfo(Path.GetTempFileName());
+
+            try
+            {
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Download(completeRemotePath, fileInfo);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {completeRemotePath}: No such file or directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                fileInfo.Delete();
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadFileInfoExistingDirectoryData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_FileInfo_ExistingDirectory()
+        {
+            foreach (var data in GetScpDownloadFileInfoExistingDirectoryData())
+            {
+                Scp_Download_FileInfo_ExistingDirectory((IRemotePathTransformation)data[0],
+                                                        (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Download_FileInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation,
+                                                            string remotePath)
+        {
+            // remove complete directory if it's not the home directory of the user
+            // or else remove the remote file
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+
+                    client.CreateDirectory(remotePath);
+                }
+            }
+
+            var fileInfo = new FileInfo(Path.GetTempFileName());
+            fileInfo.Delete();
+
+            try
+            {
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Download(remotePath, fileInfo);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remotePath}: not a regular file", ex.Message);
+                    }
+
+                    Assert.IsFalse(fileInfo.Exists);
+                }
+            }
+            finally
+            {
+                fileInfo.Delete();
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadFileInfoExistingFileData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_FileInfo_ExistingFile()
+        {
+            foreach (var data in GetScpDownloadFileInfoExistingFileData())
+            {
+                Scp_Download_FileInfo_ExistingFile((IRemotePathTransformation)data[0],
+                                                        (string)data[1],
+                                                        (string)data[2],
+                                                        (int)data[3]);
+            }
+        }
+#endif
+        public void Scp_Download_FileInfo_ExistingFile(IRemotePathTransformation remotePathTransformation,
+                                                       string remotePath,
+                                                       string remoteFile,
+                                                       int size)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            // remove complete directory if it's not the home directory of the user
+            // or else remove the remote file
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+
+                    client.CreateDirectory(remotePath);
+                }
+            }
+
+            var fileInfo = new FileInfo(Path.GetTempFileName());
+
+            try
+            {
+                var content = CreateMemoryStream(size);
+                content.Position = 0;
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Upload(content, completeRemotePath);
+                }
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Download(completeRemotePath, fileInfo);
+                }
+
+                using (var fs = fileInfo.OpenRead())
+                {
+                    var downloadedBytes = new byte[fs.Length];
+                    Assert.AreEqual(downloadedBytes.Length, fs.Read(downloadedBytes, 0, downloadedBytes.Length));
+                    content.Position = 0;
+                    Assert.AreEqual(CreateHash(content), CreateHash(downloadedBytes));
+                }
+            }
+            finally
+            {
+                fileInfo.Delete();
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadStreamExistingDirectoryData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_Stream_ExistingDirectory()
+        {
+            foreach (var data in GetScpDownloadStreamExistingDirectoryData())
+            {
+                Scp_Download_Stream_ExistingDirectory((IRemotePathTransformation)data[0],
+                                                      (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Download_Stream_ExistingDirectory(IRemotePathTransformation remotePathTransformation,
+                                                          string remotePath)
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+
+                    client.CreateDirectory(remotePath);
+                }
+            }
+
+            var file = Path.GetTempFileName();
+            File.Delete(file);
+
+            try
+            {
+                using (var fs = File.OpenWrite(file))
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Download(remotePath, fs);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remotePath}: not a regular file", ex.Message);
+                    }
+
+                    Assert.AreEqual(0, fs.Length);
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpDownloadStreamExistingFileData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Download_Stream_ExistingFile()
+        {
+            foreach (var data in GetScpDownloadStreamExistingFileData())
+            {
+                Scp_Download_Stream_ExistingFile((IRemotePathTransformation)data[0],
+                                                 (string)data[1],
+                                                 (string)data[2],
+                                                 (int)data[3]);
+            }
+        }
+#endif
+        public void Scp_Download_Stream_ExistingFile(IRemotePathTransformation remotePathTransformation,
+                                                     string remotePath,
+                                                     string remoteFile,
+                                                     int size)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            // remove complete directory if it's not the home directory of the user
+            // or else remove the remote file
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+
+                    client.CreateDirectory(remotePath);
+                }
+            }
+
+            var file = CreateTempFile(size);
+
+            try
+            {
+                using (var fs = File.OpenRead(file))
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Upload(fs, completeRemotePath);
+                }
+
+                using (var fs = File.OpenRead(file))
+                using (var downloaded = new MemoryStream(size))
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    client.Download(completeRemotePath, downloaded);
+                    downloaded.Position = 0;
+                    Assert.AreEqual(CreateHash(fs), CreateHash(downloaded));
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadFileStreamDirectoryDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileStream_DirectoryDoesNotExist()
+        {
+            foreach (var data in GetScpUploadFileStreamDirectoryDoesNotExistData())
+            {
+                Scp_Upload_FileStream_DirectoryDoesNotExist((IRemotePathTransformation)data[0],
+                                                            (string)data[1],
+                                                            (string)data[2]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileStream_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                                string remotePath,
+                                                                string remoteFile)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                if (client.Exists(remotePath))
+                {
+                    client.DeleteDirectory(remotePath);
+                }
+            }
+
+            var file = CreateTempFile(1000);
+
+            try
+            {
+                using (var fs = File.OpenRead(file))
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Upload(fs, completeRemotePath);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remotePath}: No such file or directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadFileStreamExistingDirectoryData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileStream_ExistingDirectory()
+        {
+            foreach (var data in GetScpUploadFileStreamExistingDirectoryData())
+            {
+                Scp_Upload_FileStream_ExistingDirectory((IRemotePathTransformation)data[0],
+                                                        (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileStream_ExistingDirectory(IRemotePathTransformation remotePathTransformation,
+                                                            string remoteFile)
+        {
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteFile)))
+                {
+                    command.Execute();
+                }
+            }
+
+            var file = CreateTempFile(1000);
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+                    client.CreateDirectory(remoteFile);
+                }
+
+                using (var fs = File.OpenRead(file))
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Upload(fs, remoteFile);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remoteFile}: Is a directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteDirectory(remoteFile);
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(ScpUploadFileStreamExistingFileData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileStream_ExistingFile()
+        {
+            foreach (var data in ScpUploadFileStreamExistingFileData())
+            {
+                Scp_Upload_FileStream_ExistingFile((IRemotePathTransformation)data[0],
+                                                   (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileStream_ExistingFile(IRemotePathTransformation remotePathTransformation,
+                                                       string remoteFile)
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+            }
+
+            // original content is bigger than new content to ensure file is fully overwritten
+            var originalContent = CreateMemoryStream(2000);
+            var file = CreateTempFile(1000);
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    originalContent.Position = 0;
+                    client.UploadFile(originalContent, remoteFile);
+                }
+
+                using (var fs = File.OpenRead(file))
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Upload(fs, remoteFile);
+                }
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    using (var downloaded = new MemoryStream(1000))
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateFileHash(file), CreateHash(downloaded));
+                    }
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadFileStreamFileDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileStream_FileDoesNotExist()
+        {
+            foreach (var data in GetScpUploadFileStreamFileDoesNotExistData())
+            {
+                Scp_Upload_FileStream_FileDoesNotExist((IRemotePathTransformation)data[0],
+                                                       (string)data[1],
+                                                       (string)data[2],
+                                                       (int)data[3]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileStream_FileDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                           string remotePath,
+                                                           string remoteFile,
+                                                           int size)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                // remove complete directory if it's not the home directory of the user
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+
+            var file = CreateTempFile(size);
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    // create directory if it's not the home directory of the user
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (!client.Exists((remotePath)))
+                        {
+                            client.CreateDirectory(remotePath);
+                        }
+                    }
+                }
+
+                using (var fs = File.OpenRead(file))
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Upload(fs, completeRemotePath);
+                }
+
+                using (var fs = File.OpenRead(file))
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    var sftpFile = client.Get(completeRemotePath);
+                    Assert.AreEqual(GetAbsoluteRemotePath(client, remotePath, remoteFile), sftpFile.FullName);
+                    Assert.AreEqual(size, sftpFile.Length);
+
+                    var downloaded = client.ReadAllBytes(completeRemotePath);
+                    Assert.AreEqual(CreateHash(fs), CreateHash(downloaded));
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    // remove complete directory if it's not the home directory of the user
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// https://github.com/sshnet/SSH.NET/issues/289
+        /// </summary>
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadFileInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileInfo_DirectoryDoesNotExist()
+        {
+            foreach (var data in GetScpUploadFileInfoDirectoryDoesNotExistData())
+            {
+                Scp_Upload_FileInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0],
+                                                          (string)data[1],
+                                                          (string)data[2]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                              string remotePath,
+                                                              string remoteFile)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                if (client.Exists(remotePath))
+                {
+                    client.DeleteDirectory(remotePath);
+                }
+            }
+
+            var file = CreateTempFile(1000);
+
+            try
+            {
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Upload(new FileInfo(file), completeRemotePath);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remotePath}: No such file or directory", ex.Message);
+                    }
+                }
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    Assert.IsFalse(client.Exists(completeRemotePath));
+                    Assert.IsFalse(client.Exists(remotePath));
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.DeleteFile(completeRemotePath);
+                    }
+
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// https://github.com/sshnet/SSH.NET/issues/286
+        /// </summary>
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadFileInfoExistingDirectoryData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileInfo_ExistingDirectory()
+        {
+            foreach (var data in GetScpUploadFileInfoExistingDirectoryData())
+            {
+                Scp_Upload_FileInfo_ExistingDirectory((IRemotePathTransformation)data[0],
+                                                      (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation,
+                                                          string remoteFile)
+        {
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteFile)))
+                {
+                    command.Execute();
+                }
+            }
+
+            var file = CreateTempFile(1000);
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+                    client.CreateDirectory(remoteFile);
+                }
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Upload(new FileInfo(file), remoteFile);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remoteFile}: Is a directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SshClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteFile)))
+                    {
+                        command.Execute();
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadFileInfoExistingFileData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileInfo_ExistingFile()
+        {
+            foreach (var data in GetScpUploadFileInfoExistingFileData())
+            {
+                Scp_Upload_FileInfo_ExistingFile((IRemotePathTransformation)data[0],
+                                                 (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileInfo_ExistingFile(IRemotePathTransformation remotePathTransformation,
+                                                     string remoteFile)
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+            }
+
+            // original content is bigger than new content to ensure file is fully overwritten
+            var originalContent = CreateMemoryStream(2000);
+            var file = CreateTempFile(1000);
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    originalContent.Position = 0;
+                    client.UploadFile(originalContent, remoteFile);
+                }
+
+                var fileInfo = new FileInfo(file)
+                    {
+                        LastAccessTimeUtc = new DateTime(1973, 8, 13, 20, 15, 33, DateTimeKind.Utc),
+                        LastWriteTimeUtc = new DateTime(1974, 1, 24, 3, 55, 12, DateTimeKind.Utc)
+                    };
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Upload(fileInfo, remoteFile);
+                }
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    var uploadedFile = client.Get(remoteFile);
+                    Assert.AreEqual(fileInfo.LastAccessTimeUtc, uploadedFile.LastAccessTimeUtc);
+                    Assert.AreEqual(fileInfo.LastWriteTimeUtc, uploadedFile.LastWriteTimeUtc);
+
+                    using (var downloaded = new MemoryStream(1000))
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateFileHash(file), CreateHash(downloaded));
+                    }
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadFileInfoFileDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_FileInfo_FileDoesNotExist()
+        {
+            foreach (var data in GetScpUploadFileInfoFileDoesNotExistData())
+            {
+                Scp_Upload_FileInfo_FileDoesNotExist((IRemotePathTransformation)data[0],
+                                                     (string)data[1],
+                                                     (string)data[2],
+                                                     (int)data[3]);
+            }
+        }
+#endif
+        public void Scp_Upload_FileInfo_FileDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                         string remotePath,
+                                                         string remoteFile,
+                                                         int size)
+        {
+            var completeRemotePath = CombinePaths(remotePath, remoteFile);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(completeRemotePath))
+                {
+                    client.DeleteFile(completeRemotePath);
+                }
+
+                // remove complete directory if it's not the home directory of the user
+                if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                {
+                    if (client.Exists(remotePath))
+                    {
+                        client.DeleteDirectory(remotePath);
+                    }
+                }
+            }
+
+            var file = CreateTempFile(size);
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    // create directory if it's not the home directory of the user
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (!client.Exists(remotePath))
+                        {
+                            client.CreateDirectory(remotePath);
+                        }
+                    }
+                }
+
+                var fileInfo = new FileInfo(file)
+                    {
+                        LastAccessTimeUtc = new DateTime(1973, 8, 13, 20, 15, 33, DateTimeKind.Utc),
+                        LastWriteTimeUtc = new DateTime(1974, 1, 24, 3, 55, 12, DateTimeKind.Utc)
+                    };
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Upload(fileInfo, completeRemotePath);
+                }
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    var uploadedFile = client.Get(completeRemotePath);
+                    Assert.AreEqual(fileInfo.LastAccessTimeUtc, uploadedFile.LastAccessTimeUtc);
+                    Assert.AreEqual(fileInfo.LastWriteTimeUtc, uploadedFile.LastWriteTimeUtc);
+                    Assert.AreEqual(size, uploadedFile.Length);
+
+                    using (var downloaded = new MemoryStream(size))
+                    {
+                        client.DownloadFile(completeRemotePath, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateFileHash(file), CreateHash(downloaded));
+                    }
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(completeRemotePath))
+                    {
+                        client.Delete(completeRemotePath);
+                    }
+
+                    // remove complete directory if it's not the home directory of the user
+                    if (remotePath.Length > 0 && remotePath != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remotePath))
+                        {
+                            client.DeleteDirectory(remotePath);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadDirectoryInfoDirectoryDoesNotExistData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_DirectoryInfo_DirectoryDoesNotExist()
+        {
+            foreach (var data in GetScpUploadDirectoryInfoDirectoryDoesNotExistData())
+            {
+                Scp_Upload_DirectoryInfo_DirectoryDoesNotExist((IRemotePathTransformation)data[0],
+                                                               (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Upload_DirectoryInfo_DirectoryDoesNotExist(IRemotePathTransformation remotePathTransformation,
+                                                                   string remoteDirectory)
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists((remoteDirectory)))
+                {
+                    client.DeleteDirectory(remoteDirectory);
+                }
+            }
+
+            var localDirectory = Path.GetTempFileName();
+            File.Delete(localDirectory);
+            Directory.CreateDirectory(localDirectory);
+
+            try
+            {
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    try
+                    {
+                        client.Upload(new DirectoryInfo(localDirectory), remoteDirectory);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remoteDirectory}: No such file or directory", ex.Message);
+                    }
+                }
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    Assert.IsFalse(client.Exists(remoteDirectory));
+                }
+            }
+            finally
+            {
+                Directory.Delete(localDirectory, true);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists((remoteDirectory)))
+                    {
+                        client.DeleteDirectory(remoteDirectory);
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadDirectoryInfoExistingDirectoryData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_DirectoryInfo_ExistingDirectory()
+        {
+            foreach (var data in GetScpUploadDirectoryInfoExistingDirectoryData())
+            {
+                Scp_Upload_DirectoryInfo_ExistingDirectory((IRemotePathTransformation)data[0],
+                                                           (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Upload_DirectoryInfo_ExistingDirectory(IRemotePathTransformation remotePathTransformation,
+                                                               string remoteDirectory)
+        {
+            string absoluteRemoteDirectory = GetAbsoluteRemotePath(_connectionInfoFactory, remoteDirectory);
+
+            var remotePathFile1 = CombinePaths(remoteDirectory, "file1");
+            var remotePathFile2 = CombinePaths(remoteDirectory, "file2");
+
+            var absoluteremoteSubDirectory1 = CombinePaths(absoluteRemoteDirectory, "sub1");
+            var remoteSubDirectory1 = CombinePaths(remoteDirectory, "sub1");
+            var remotePathSubFile1 = CombinePaths(remoteSubDirectory1, "file1");
+            var remotePathSubFile2 = CombinePaths(remoteSubDirectory1, "file2");
+            var absoluteremoteSubDirectory2 = CombinePaths(absoluteRemoteDirectory, "sub2");
+            var remoteSubDirectory2 = CombinePaths(remoteDirectory, "sub2");
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remotePathSubFile1))
+                {
+                    client.DeleteFile(remotePathSubFile1);
+                }
+                if (client.Exists(remotePathSubFile2))
+                {
+                    client.DeleteFile(remotePathSubFile2);
+                }
+                if (client.Exists(remoteSubDirectory1))
+                {
+                    client.DeleteDirectory(remoteSubDirectory1);
+                }
+                if (client.Exists(remoteSubDirectory2))
+                {
+                    client.DeleteDirectory(remoteSubDirectory2);
+                }
+                if (client.Exists(remotePathFile1))
+                {
+                    client.DeleteFile(remotePathFile1);
+                }
+                if (client.Exists(remotePathFile2))
+                {
+                    client.DeleteFile(remotePathFile2);
+                }
+
+                if (remoteDirectory.Length > 0 && remoteDirectory != "." && remoteDirectory != client.WorkingDirectory)
+                {
+                    if (client.Exists(remoteDirectory))
+                    {
+                        client.DeleteDirectory(remoteDirectory);
+                    }
+
+                    client.CreateDirectory(remoteDirectory);
+                }
+            }
+
+            var localDirectory = Path.GetTempFileName();
+            File.Delete(localDirectory);
+            Directory.CreateDirectory(localDirectory);
+
+            var localPathFile1 = Path.Combine(localDirectory, "file1");
+            var localPathFile2 = Path.Combine(localDirectory, "file2");
+
+            var localSubDirectory1 = Path.Combine(localDirectory, "sub1");
+            var localPathSubFile1 = Path.Combine(localSubDirectory1, "file1");
+            var localPathSubFile2 = Path.Combine(localSubDirectory1, "file2");
+            var localSubDirectory2 = Path.Combine(localDirectory, "sub2");
+
+            try
+            {
+                CreateFile(localPathFile1, 2000);
+                File.SetLastWriteTimeUtc(localPathFile1, new DateTime(2015, 8, 24, 5, 32, 16, DateTimeKind.Utc));
+
+                CreateFile(localPathFile2, 1000);
+                File.SetLastWriteTimeUtc(localPathFile2, new DateTime(2012, 3, 27, 23, 2, 54, DateTimeKind.Utc));
+
+                // create subdirectory with two files
+                Directory.CreateDirectory(localSubDirectory1);
+                CreateFile(localPathSubFile1, 1000);
+                File.SetLastWriteTimeUtc(localPathSubFile1, new DateTime(2013, 4, 12, 16, 54, 22, DateTimeKind.Utc));
+                CreateFile(localPathSubFile2, 2000);
+                File.SetLastWriteTimeUtc(localPathSubFile2, new DateTime(2015, 8, 4, 12, 43, 12, DateTimeKind.Utc));
+                Directory.SetLastWriteTimeUtc(localSubDirectory1,
+                                              new DateTime(2014, 6, 12, 13, 2, 44, DateTimeKind.Utc));
+
+                // create empty subdirectory
+                Directory.CreateDirectory(localSubDirectory2);
+                Directory.SetLastWriteTimeUtc(localSubDirectory2,
+                                              new DateTime(2011, 5, 14, 1, 5, 12, DateTimeKind.Utc));
+
+                Directory.SetLastWriteTimeUtc(localDirectory, new DateTime(2015, 10, 14, 22, 45, 11, DateTimeKind.Utc));
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+                    client.Upload(new DirectoryInfo(localDirectory), remoteDirectory);
+                }
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    Assert.IsTrue(client.Exists(remoteDirectory));
+
+                    var remoteSftpDirectory = client.Get(remoteDirectory);
+                    Assert.IsNotNull(remoteSftpDirectory);
+                    Assert.AreEqual(absoluteRemoteDirectory, remoteSftpDirectory.FullName);
+                    Assert.IsTrue(remoteSftpDirectory.IsDirectory);
+                    Assert.IsFalse(remoteSftpDirectory.IsRegularFile);
+
+                    // Due to CVE-2018-20685, we can no longer set the times or modes on a file or directory
+                    // that refers to the current directory ('.'), the parent directory ('..') or a directory
+                    // containing a forward slash ('/').
+                    Assert.AreNotEqual(Directory.GetLastWriteTimeUtc(localDirectory), remoteSftpDirectory.LastWriteTimeUtc);
+
+                    Assert.IsTrue(client.Exists(remotePathFile1));
+                    Assert.AreEqual(CreateFileHash(localPathFile1), CreateRemoteFileHash(client, remotePathFile1));
+                    var remoteSftpFile = client.Get(remotePathFile1);
+                    Assert.IsNotNull(remoteSftpFile);
+                    Assert.IsFalse(remoteSftpFile.IsDirectory);
+                    Assert.IsTrue(remoteSftpFile.IsRegularFile);
+                    Assert.AreEqual(File.GetLastWriteTimeUtc(localPathFile1), remoteSftpFile.LastWriteTimeUtc);
+
+                    Assert.IsTrue(client.Exists(remotePathFile2));
+                    Assert.AreEqual(CreateFileHash(localPathFile2), CreateRemoteFileHash(client, remotePathFile2));
+                    remoteSftpFile = client.Get(remotePathFile2);
+                    Assert.IsNotNull(remoteSftpFile);
+                    Assert.IsFalse(remoteSftpFile.IsDirectory);
+                    Assert.IsTrue(remoteSftpFile.IsRegularFile);
+                    Assert.AreEqual(File.GetLastWriteTimeUtc(localPathFile2), remoteSftpFile.LastWriteTimeUtc);
+
+                    Assert.IsTrue(client.Exists(remoteSubDirectory1));
+                    remoteSftpDirectory = client.Get(remoteSubDirectory1);
+                    Assert.IsNotNull(remoteSftpDirectory);
+                    Assert.AreEqual(absoluteremoteSubDirectory1, remoteSftpDirectory.FullName);
+                    Assert.IsTrue(remoteSftpDirectory.IsDirectory);
+                    Assert.IsFalse(remoteSftpDirectory.IsRegularFile);
+                    Assert.AreEqual(Directory.GetLastWriteTimeUtc(localSubDirectory1), remoteSftpDirectory.LastWriteTimeUtc);
+
+                    Assert.IsTrue(client.Exists(remotePathSubFile1));
+                    Assert.AreEqual(CreateFileHash(localPathSubFile1), CreateRemoteFileHash(client, remotePathSubFile1));
+
+                    Assert.IsTrue(client.Exists(remotePathSubFile2));
+                    Assert.AreEqual(CreateFileHash(localPathSubFile2), CreateRemoteFileHash(client, remotePathSubFile2));
+
+                    Assert.IsTrue(client.Exists(remoteSubDirectory2));
+                    remoteSftpDirectory = client.Get(remoteSubDirectory2);
+                    Assert.IsNotNull(remoteSftpDirectory);
+                    Assert.AreEqual(absoluteremoteSubDirectory2, remoteSftpDirectory.FullName);
+                    Assert.IsTrue(remoteSftpDirectory.IsDirectory);
+                    Assert.IsFalse(remoteSftpDirectory.IsRegularFile);
+                    Assert.AreEqual(Directory.GetLastWriteTimeUtc(localSubDirectory2), remoteSftpDirectory.LastWriteTimeUtc);
+                }
+            }
+            finally
+            {
+                Directory.Delete(localDirectory, true);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remotePathSubFile1))
+                    {
+                        client.DeleteFile(remotePathSubFile1);
+                    }
+                    if (client.Exists(remotePathSubFile2))
+                    {
+                        client.DeleteFile(remotePathSubFile2);
+                    }
+                    if (client.Exists(remoteSubDirectory1))
+                    {
+                        client.DeleteDirectory(remoteSubDirectory1);
+                    }
+                    if (client.Exists(remoteSubDirectory2))
+                    {
+                        client.DeleteDirectory(remoteSubDirectory2);
+                    }
+                    if (client.Exists(remotePathFile1))
+                    {
+                        client.DeleteFile(remotePathFile1);
+                    }
+                    if (client.Exists(remotePathFile2))
+                    {
+                        client.DeleteFile(remotePathFile2);
+                    }
+
+                    if (remoteDirectory.Length > 0 && remoteDirectory != "." && remoteDirectory != client.WorkingDirectory)
+                    {
+                        if (client.Exists(remoteDirectory))
+                        {
+                            client.DeleteDirectory(remoteDirectory);
+                        }
+                    }
+                }
+            }
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetScpUploadDirectoryInfoExistingFileData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Scp_Upload_DirectoryInfo_ExistingFile()
+        {
+            foreach (var data in GetScpUploadDirectoryInfoExistingFileData())
+            {
+                Scp_Upload_DirectoryInfo_ExistingFile((IRemotePathTransformation)data[0],
+                                                      (string)data[1]);
+            }
+        }
+#endif
+        public void Scp_Upload_DirectoryInfo_ExistingFile(IRemotePathTransformation remotePathTransformation,
+                                                          string remoteDirectory)
+        {
+            var remotePathFile1 = CombinePaths(remoteDirectory, "file1");
+            var remotePathFile2 = CombinePaths(remoteDirectory, "file2");
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                Console.WriteLine(client.ConnectionInfo.CurrentKeyExchangeAlgorithm);
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+            }
+
+            var localDirectory = Path.GetTempFileName();
+            File.Delete(localDirectory);
+            Directory.CreateDirectory(localDirectory);
+
+            var localPathFile1 = Path.Combine(localDirectory, "file1");
+            var localPathFile2 = Path.Combine(localDirectory, "file2");
+
+            try
+            {
+                CreateFile(localPathFile1, 50);
+                CreateFile(localPathFile2, 50);
+
+                using (var client = new ScpClient(_connectionInfoFactory.Create()))
+                {
+                    if (remotePathTransformation != null)
+                    {
+                        client.RemotePathTransformation = remotePathTransformation;
+                    }
+
+                    client.Connect();
+
+                    CreateRemoteFile(client, remoteDirectory, 10);
+
+                    try
+                    {
+                        client.Upload(new DirectoryInfo(localDirectory), remoteDirectory);
+                        Assert.Fail();
+                    }
+                    catch (ScpException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual($"scp: {remoteDirectory}: Not a directory", ex.Message);
+                    }
+                }
+            }
+            finally
+            {
+                Directory.Delete(localDirectory, true);
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    if (client.Exists(remotePathFile1))
+                    {
+                        client.DeleteFile(remotePathFile1);
+                    }
+                    if (client.Exists(remotePathFile2))
+                    {
+                        client.DeleteFile(remotePathFile2);
+                    }
+                    if (client.Exists((remoteDirectory)))
+                    {
+                        client.DeleteFile(remoteDirectory);
+                    }
+                }
+            }
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadStreamDirectoryDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-directorydoesnotexist", "scp-file" };
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-directorydoesnotexist", "scp-file" };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadFileInfoFileDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "test123", 0 };
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "test123", 5 * 1024 * 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 };
+            yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 };
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 };
+            yield return new object[] { null, "", "scp-issue280", 1024 };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadFileStreamFileDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 0 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 };
+            yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 };
+            yield return new object[] { RemotePathTransformation.None, "", "scp-issue280", 1024 };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadDirectoryInfoExistingDirectoryData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "scp-directorydoesnotexist" };
+            yield return new object[] { RemotePathTransformation.None, "." };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%" };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadDirectoryInfoExistingFileData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "scp-upload-file" };
+        }
+
+        private static IEnumerable<object[]> ScpUploadFileStreamExistingFileData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-upload-file" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadStreamFileDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "scp-filedoesnotexist" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadDirectoryInfoDirectoryDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-download" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadDirectoryInfoExistingFileData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "scp-download" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadDirectoryInfoExistingDirectoryData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "scp-download" };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'space \\tab\tlf*?[#~=%" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadFileInfoDirectoryDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-directorydoesnotexist", "scp-file" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadFileInfoFileDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet", "scp-filedoesnotexist" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadFileInfoExistingDirectoryData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-test" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadFileInfoExistingFileData()
+        {
+            yield return new object[] { null, "", "file 123", 0 };
+            yield return new object[] { null, "", "file 123", 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 };
+            yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadStreamExistingDirectoryData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-test" };
+        }
+
+        private static IEnumerable<object[]> GetScpDownloadStreamExistingFileData()
+        {
+            yield return new object[] { null, "", "file 123", 0 };
+            yield return new object[] { null, "", "file 123", 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 };
+            yield return new object[] { null, "/home/sshnet/scp test", "file 123", 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/dir|&;<>()$`\"'sp\u0100ce \\tab\tlf\n*?[#~=%", "file123", 1024 };
+            yield return new object[] { RemotePathTransformation.ShellQuote, "/home/sshnet/scp-test", "file|&;<>()$`\"'sp\u0100ce \\tab\tlf*?[#~=%", 1024 };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadFileStreamDirectoryDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue289", "file123" };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadFileStreamExistingDirectoryData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue286" };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadFileInfoDirectoryDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue289", "file123" };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadFileInfoExistingDirectoryData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-issue286" };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadFileInfoExistingFileData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "/home/sshnet/scp-upload-file" };
+        }
+
+        private static IEnumerable<object[]> GetScpUploadDirectoryInfoDirectoryDoesNotExistData()
+        {
+            yield return new object[] { RemotePathTransformation.None, "scp-directorydoesnotexist" };
+        }
+
+        private static void CreateRemoteFile(ScpClient client, string remoteFile, int size)
+        {
+            var file = CreateTempFile(size);
+
+            try
+            {
+                using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
+                {
+                    client.Upload(fs, remoteFile);
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+            }
+        }
+
+        private static string GetAbsoluteRemotePath(SftpClient client, string directoryName, string fileName)
+        {
+            var absolutePath = string.Empty;
+
+            if (directoryName.Length == 0)
+            {
+                absolutePath += client.WorkingDirectory;
+            }
+            else
+            {
+                if (directoryName[0] != '/')
+                {
+                    absolutePath += client.WorkingDirectory + "/" + directoryName;
+                }
+                else
+                {
+                    absolutePath = directoryName;
+                }
+            }
+
+            return absolutePath + "/" + fileName;
+        }
+
+        private static string GetAbsoluteRemotePath(IConnectionInfoFactory connectionInfoFactory, string directoryName)
+        {
+            var absolutePath = string.Empty;
+
+            if (directoryName.Length == 0 || directoryName == ".")
+            {
+                using (var client = new SftpClient(connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    absolutePath += client.WorkingDirectory;
+                }
+            }
+            else
+            {
+                if (directoryName[0] != '/')
+                {
+                    using (var client = new SftpClient(connectionInfoFactory.Create()))
+                    {
+                        client.Connect();
+
+                        absolutePath += client.WorkingDirectory + "/" + directoryName;
+                    }
+                }
+                else
+                {
+                    absolutePath = directoryName;
+                }
+            }
+
+            return absolutePath;
+        }
+
+        private static string CreateRemoteFileHash(SftpClient client, string remotePath)
+        {
+            using (var fs = client.OpenRead(remotePath))
+            {
+                return CreateHash(fs);
+            }
+        }
+
+        private static string CombinePaths(string path1, string path2)
+        {
+            if (path1.Length == 0)
+            {
+                return path2;
+            }
+
+            if (path2.Length == 0)
+            {
+                return path1;
+            }
+
+            return path1 + "/" + path2;
+        }
+    }
+}

+ 102 - 0
src/Renci.SshNet.IntegrationTests/SftpClientTests.cs

@@ -0,0 +1,102 @@
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    /// <summary>
+    /// The SFTP client integration tests
+    /// </summary>
+    [TestClass]
+    public class SftpClientTests : IntegrationTestBase, IDisposable
+    {
+        private readonly SftpClient _sftpClient;
+
+        public SftpClientTests()
+        {
+            _sftpClient = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
+            _sftpClient.Connect();
+        }
+
+        [TestMethod]
+        public void Create_directory_with_contents_and_list_it()
+        {
+            var testDirectory = "/home/sshnet/sshnet-test";
+            var testFileName =  "test-file.txt";
+            var testFilePath = $"{testDirectory}/{testFileName}";
+            var testContent = "file content";
+
+            // Create new directory and check if it exists
+            _sftpClient.CreateDirectory(testDirectory);
+            Assert.IsTrue(_sftpClient.Exists(testDirectory));
+
+            // Upload file and check if it exists
+            using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+            _sftpClient.UploadFile(fileStream, testFilePath);
+            Assert.IsTrue(_sftpClient.Exists(testFilePath));
+
+            // Check if ListDirectory works
+            var files = _sftpClient.ListDirectory(testDirectory);
+
+            _sftpClient.DeleteFile(testFilePath);
+            _sftpClient.DeleteDirectory(testDirectory);
+
+            var builder = new StringBuilder();
+            foreach (var file in files)
+            {
+                builder.AppendLine($"{file.FullName} {file.IsRegularFile} {file.IsDirectory}");
+            }
+
+            Assert.AreEqual(@"/home/sshnet/sshnet-test/. False True
+/home/sshnet/sshnet-test/.. False True
+/home/sshnet/sshnet-test/test-file.txt True False
+", builder.ToString());
+        }
+
+        [TestMethod]
+        public async Task Create_directory_with_contents_and_list_it_async()
+        {
+            var testDirectory = "/home/sshnet/sshnet-test";
+            var testFileName = "test-file.txt";
+            var testFilePath = $"{testDirectory}/{testFileName}";
+            var testContent = "file content";
+
+            // Create new directory and check if it exists
+            _sftpClient.CreateDirectory(testDirectory);
+            Assert.IsTrue(_sftpClient.Exists(testDirectory));
+
+            // Upload file and check if it exists
+            using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+            _sftpClient.UploadFile(fileStream, testFilePath);
+            Assert.IsTrue(_sftpClient.Exists(testFilePath));
+
+            // Check if ListDirectory works
+            var files = _sftpClient.ListDirectoryAsync(testDirectory, CancellationToken.None);
+
+            var builder = new StringBuilder();
+            await foreach (var file in files)
+            {
+                builder.AppendLine($"{file.FullName} {file.IsRegularFile} {file.IsDirectory}");
+            }
+
+            _sftpClient.DeleteFile(testFilePath);
+            _sftpClient.DeleteDirectory(testDirectory);
+
+            Assert.AreEqual(@"/home/sshnet/sshnet-test/. False True
+/home/sshnet/sshnet-test/.. False True
+/home/sshnet/sshnet-test/test-file.txt True False
+", builder.ToString());
+        }
+
+        [TestMethod]
+        [ExpectedException(typeof(SftpPermissionDeniedException), "Permission denied")]
+        public void Test_Sftp_ListDirectory_Permission_Denied()
+        {
+            _sftpClient.ListDirectory("/root");
+        }
+
+        public void Dispose()
+        {
+            _sftpClient.Disconnect();
+            _sftpClient.Dispose();
+        }
+    }
+}

+ 6360 - 0
src/Renci.SshNet.IntegrationTests/SftpTests.cs

@@ -0,0 +1,6360 @@
+using System.Diagnostics;
+
+using Renci.SshNet.Common;
+using Renci.SshNet.IntegrationTests.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    // TODO: DeleteDirectory (fail + success
+    // TODO: DeleteFile (fail + success
+    // TODO: Delete (fail + success
+
+    [TestClass]
+    public class SftpTests : TestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private IConnectionInfoFactory _adminConnectionInfoFactory;
+        private IRemotePathTransformation _remotePathTransformation;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
+            _remotePathTransformation = RemotePathTransformation.ShellQuote;
+        }
+
+#if FEATURE_MSTEST_DATATEST
+        [DataTestMethod]
+        [DynamicData(nameof(GetSftpUploadFileFileStreamData), DynamicDataSourceType.Method)]
+#else
+        [TestMethod]
+        public void Sftp_Upload_DirectoryInfo_ExistingFile()
+        {
+            foreach (var data in GetSftpUploadFileFileStreamData())
+            {
+                Sftp_UploadFile_FileStream((int) data[0]);
+            }
+        }
+#endif
+        public void Sftp_UploadFile_FileStream(int size)
+        {
+            var file = CreateTempFile(size);
+
+            using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.Delete(remoteFile);
+                }
+
+                try
+                {
+                    client.UploadFile(fs, remoteFile);
+
+                    using (var memoryStream = new MemoryStream(size))
+                    {
+                        client.DownloadFile(remoteFile, memoryStream);
+                        memoryStream.Position = 0;
+                        Assert.AreEqual(CreateFileHash(file), CreateHash(memoryStream));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.Delete(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ConnectDisconnect_Serial()
+        {
+            const int iterations = 100;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                for (var i = 1; i <= iterations; i++)
+                {
+                    client.Connect();
+                    client.Disconnect();
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ConnectDisconnect_Parallel()
+        {
+            const int iterations = 10;
+            const int threads = 20;
+
+            var startEvent = new ManualResetEvent(false);
+
+            var tasks = Enumerable.Range(1, threads).Select(i =>
+                {
+                    return Task.Factory.StartNew(() =>
+                    {
+                        using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                        {
+                            startEvent.WaitOne();
+
+                            for (var j = 0; j < iterations; j++)
+                            {
+                                client.Connect();
+                                client.Disconnect();
+                            }
+                        }
+                    });
+                }).ToArray();
+
+            startEvent.Set();
+
+            Task.WaitAll(tasks);
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile()
+        {
+            const string content = "SftpBeginUploadFile";
+
+            var expectedByteCount = (ulong) Encoding.ASCII.GetByteCount(content);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.Delete(remoteFile);
+                }
+
+                try
+                {
+                    using (var memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(content)))
+                    {
+                        IAsyncResult asyncResultCallback = null;
+
+                        var asyncResult = client.BeginUploadFile(memoryStream, remoteFile, ar => asyncResultCallback = ar);
+
+                        Assert.IsTrue(asyncResult.AsyncWaitHandle.WaitOne(10000));
+
+                        // check async result
+                        var sftpUploadAsyncResult = asyncResult as SftpUploadAsyncResult;
+                        Assert.IsNotNull(sftpUploadAsyncResult);
+                        Assert.IsFalse(sftpUploadAsyncResult.IsUploadCanceled);
+                        Assert.IsTrue(sftpUploadAsyncResult.IsCompleted);
+                        Assert.IsFalse(sftpUploadAsyncResult.CompletedSynchronously);
+                        Assert.AreEqual(expectedByteCount, sftpUploadAsyncResult.UploadedBytes);
+
+                        // check async result callback
+                        var sftpUploadAsyncResultCallback = asyncResultCallback as SftpUploadAsyncResult;
+                        Assert.IsNotNull(sftpUploadAsyncResultCallback);
+                        Assert.IsFalse(sftpUploadAsyncResultCallback.IsUploadCanceled);
+                        Assert.IsTrue(sftpUploadAsyncResultCallback.IsCompleted);
+                        Assert.IsFalse(sftpUploadAsyncResultCallback.CompletedSynchronously);
+                        Assert.AreEqual(expectedByteCount, sftpUploadAsyncResultCallback.UploadedBytes);
+                    }
+
+                    // check uploaded file
+                    using (var memoryStream = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, memoryStream);
+                        memoryStream.Position = 0;
+                        var remoteContent = Encoding.ASCII.GetString(memoryStream.ToArray());
+                        Assert.AreEqual(content, remoteContent);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.Delete(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Create_ExistingFile()
+        {
+            var encoding = Encoding.UTF8;
+            var initialContent = "Gert & Ann & Lisa";
+            var newContent1 = "Sofie";
+            var newContent1Bytes = GetBytesWithPreamble(newContent1, encoding);
+            var newContent2 = "Lisa & Sofie";
+            var newContent2Bytes = GetBytesWithPreamble(newContent2, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    using (var fs = client.Create(remoteFile))
+                    using (var sw = new StreamWriter(fs, encoding))
+                    {
+                        sw.Write(newContent1);
+                    }
+
+                    var actualContent1 = client.ReadAllBytes(remoteFile);
+                    Assert.IsTrue(newContent1Bytes.IsEqualTo(actualContent1));
+
+                    #endregion Write less bytes than the current content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    using (var fs = client.Create(remoteFile))
+                    using (var sw = new StreamWriter(fs, encoding))
+                    {
+                        sw.Write(newContent2);
+                    }
+
+                    var actualContent2 = client.ReadAllBytes(remoteFile);
+                    Assert.IsTrue(newContent2Bytes.IsEqualTo(actualContent2));
+
+                    #endregion Write more bytes than the current content, overwriting and appending to that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Create_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                SftpFileStream fs = null;
+
+                try
+                {
+                    fs = client.Create(remoteFile);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    fs?.Dispose();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Create_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.BufferSize = 512 * 1024;
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var imageStream = GetResourceStream("Renci.SshNet.IntegrationTests.resources.issue #70.png"))
+                    {
+                        using (var fs = client.Create(remoteFile))
+                        {
+                            byte[] buffer = new byte[Math.Min(client.BufferSize, imageStream.Length)];
+                            int bytesRead;
+
+                            while ((bytesRead = imageStream.Read(buffer, offset: 0, buffer.Length)) > 0)
+                            {
+                                fs.Write(buffer, offset: 0, bytesRead);
+                            }
+                        }
+
+                        using (var memoryStream = new MemoryStream())
+                        {
+                            client.DownloadFile(remoteFile, memoryStream);
+
+                            memoryStream.Position = 0;
+                            imageStream.Position = 0;
+
+                            Assert.AreEqual(CreateHash(imageStream), CreateHash(memoryStream));
+                        }
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllLines_NoEncoding_ExistingFile()
+        {
+            var initialContent = "\u0100ert & Ann";
+            IEnumerable<string> linesToAppend = new[] { "Forever", "&", "\u0116ver" };
+            var expectedContent = initialContent + string.Join(Environment.NewLine, linesToAppend) +
+                                  Environment.NewLine;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+                    client.AppendAllLines(remoteFile, linesToAppend);
+
+                    var text = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(expectedContent, text);
+
+                    // ensure we didn't write an UTF-8 BOM
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var firstByte = fs.ReadByte();
+                        Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllLines_NoEncoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            IEnumerable<string> linesToAppend = new[] { "\u0139isa", "&", "Sofie" };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllLines(remoteFile, linesToAppend);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllLines_NoEncoding_FileDoesNotExist()
+        {
+            IEnumerable<string> linesToAppend = new[] { "\u0139isa", "&", "Sofie" };
+            var expectedContent = string.Join(Environment.NewLine, linesToAppend) + Environment.NewLine;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllLines(remoteFile, linesToAppend);
+
+                    var text = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(expectedContent, text);
+
+                    // ensure we didn't write an UTF-8 BOM
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var firstByte = fs.ReadByte();
+                        Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllText_NoEncoding_ExistingFile()
+        {
+            var initialContent = "\u0100ert & Ann";
+            var contentToAppend = "Forever&\u0116ver";
+            var expectedContent = initialContent + contentToAppend;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+                    client.AppendAllText(remoteFile, contentToAppend);
+
+                    var text = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(expectedContent, text);
+
+                    // ensure we didn't write an UTF-8 BOM
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var firstByte = fs.ReadByte();
+                        Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllText_NoEncoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var contentToAppend = "Forever&\u0116ver";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllText(remoteFile, contentToAppend);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllText_NoEncoding_FileDoesNotExist()
+        {
+            var contentToAppend = "Forever&\u0116ver";
+            var expectedContent = contentToAppend;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllText(remoteFile, contentToAppend);
+
+                    var text = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(expectedContent, text);
+
+                    // ensure we didn't write an UTF-8 BOM
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var firstByte = fs.ReadByte();
+                        Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendText_NoEncoding_ExistingFile()
+        {
+            var initialContent = "\u0100ert & Ann";
+            var contentToAppend = "Forever&\u0116ver";
+            var expectedContent = initialContent + contentToAppend;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+
+                    using (var sw = client.AppendText(remoteFile))
+                    {
+                        sw.Write(contentToAppend);
+                    }
+
+                    var text = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(expectedContent, text);
+
+                    // ensure we didn't write an UTF-8 BOM
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var firstByte = fs.ReadByte();
+                        Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendText_NoEncoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                StreamWriter sw = null;
+
+                try
+                {
+                    sw = client.AppendText(remoteFile);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    sw?.Dispose();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendText_NoEncoding_FileDoesNotExist()
+        {
+            var contentToAppend = "\u0100ert & Ann";
+            var expectedContent = contentToAppend;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile))
+                    {
+                        sw.Write(contentToAppend);
+                    }
+
+                    // ensure we didn't write an UTF-8 BOM
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var firstByte = fs.ReadByte();
+                        Assert.AreEqual(Encoding.UTF8.GetBytes(expectedContent)[0], firstByte);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllLines_Encoding_ExistingFile()
+        {
+            var initialContent = "\u0100ert & Ann";
+            IEnumerable<string> linesToAppend = new[] { "Forever", "&", "\u0116ver" };
+            var expectedContent = initialContent + string.Join(Environment.NewLine, linesToAppend) +
+                                  Environment.NewLine;
+            var encoding = GetRandomEncoding();
+            var expectedBytes = GetBytesWithPreamble(expectedContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+                    client.AppendAllLines(remoteFile, linesToAppend, encoding);
+
+                    var text = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(expectedContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllLines_Encoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            IEnumerable<string> linesToAppend = new[] { "Forever", "&", "\u0116ver" };
+            var encoding = GetRandomEncoding();
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllLines(remoteFile, linesToAppend, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllLines_Encoding_FileDoesNotExist()
+        {
+            IEnumerable<string> linesToAppend = new[] { "\u0139isa", "&", "Sofie" };
+            var expectedContent = string.Join(Environment.NewLine, linesToAppend) + Environment.NewLine;
+            var encoding = GetRandomEncoding();
+            var expectedBytes = GetBytesWithPreamble(expectedContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllLines(remoteFile, linesToAppend, encoding);
+
+                    var text = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(expectedContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllText_Encoding_ExistingFile()
+        {
+            var initialContent = "\u0100ert & Ann";
+            var contentToAppend = "Forever&\u0116ver";
+            var expectedContent = initialContent + contentToAppend;
+            var encoding = GetRandomEncoding();
+            var expectedBytes = GetBytesWithPreamble(expectedContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+                    client.AppendAllText(remoteFile, contentToAppend, encoding);
+
+                    var text = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(expectedContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllText_Encoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            const string contentToAppend = "Forever&\u0116ver";
+            var encoding = GetRandomEncoding();
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllText(remoteFile, contentToAppend, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendAllText_Encoding_FileDoesNotExist()
+        {
+            const string contentToAppend = "Forever&\u0116ver";
+            var expectedContent = contentToAppend;
+            var encoding = GetRandomEncoding();
+            var expectedBytes = GetBytesWithPreamble(expectedContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.AppendAllText(remoteFile, contentToAppend, encoding);
+
+                    var text = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(expectedContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendText_Encoding_ExistingFile()
+        {
+            const string initialContent = "\u0100ert & Ann";
+            const string contentToAppend = "Forever&\u0116ver";
+            var expectedContent = initialContent + contentToAppend;
+            var encoding = GetRandomEncoding();
+            var expectedBytes = GetBytesWithPreamble(expectedContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+
+                    using (var sw = client.AppendText(remoteFile, encoding))
+                    {
+                        sw.Write(contentToAppend);
+                    }
+
+                    var text = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(expectedContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendText_Encoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var encoding = GetRandomEncoding();
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                StreamWriter sw = null;
+
+                try
+                {
+                    sw = client.AppendText(remoteFile, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    sw?.Dispose();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_AppendText_Encoding_FileDoesNotExist()
+        {
+            var encoding = GetRandomEncoding();
+            const string contentToAppend = "\u0100ert & Ann";
+            var expectedBytes = GetBytesWithPreamble(contentToAppend, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile, encoding))
+                    {
+                        sw.Write(contentToAppend);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_CreateText_NoEncoding_ExistingFile()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            const string initialContent = "\u0100ert & Ann";
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            const string newContent = "\u0116ver";
+            const string expectedContent = "\u0116ver" + " & Ann";
+            var expectedContentBytes = GetBytesWithPreamble(expectedContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+
+                    using (client.CreateText(remoteFile))
+                    {
+                    }
+
+                    // verify that original content is left untouched
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes));
+                    }
+
+                    // write content that is less bytes than original content
+                    using (var sw = client.CreateText(remoteFile))
+                    {
+                        sw.Write(newContent);
+                    }
+
+                    // verify that original content is only partially overwritten
+                    var text = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(expectedContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedContentBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_CreateText_NoEncoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                StreamWriter sw = null;
+
+                try
+                {
+                    sw = client.CreateText(remoteFile);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    sw?.Dispose();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_CreateText_NoEncoding_FileDoesNotExist()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            var initialContent = "\u0100ert & Ann";
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (client.CreateText(remoteFile))
+                    {
+                    }
+
+                    // verify that empty file was created
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var file = client.GetAttributes(remoteFile);
+                    Assert.AreEqual(0, file.Size);
+
+                    client.DeleteFile(remoteFile);
+
+                    using (var sw = client.CreateText(remoteFile))
+                    {
+                        sw.Write(initialContent);
+                    }
+
+                    // verify that content is written to file
+                    var text = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(initialContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_CreateText_Encoding_ExistingFile()
+        {
+            var encoding = GetRandomEncoding();
+            var initialContent = "\u0100ert & Ann";
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            var newContent = "\u0116ver";
+            var expectedContent = "\u0116ver" + " & Ann";
+            var expectedContentBytes = GetBytesWithPreamble(expectedContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+
+                    using (client.CreateText(remoteFile))
+                    {
+                    }
+
+                    // verify that original content is left untouched
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes));
+                    }
+
+                    // write content that is less bytes than original content
+                    using (var sw = client.CreateText(remoteFile, encoding))
+                    {
+                        sw.Write(newContent);
+                    }
+
+                    // verify that original content is only partially overwritten
+                    var text = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(expectedContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedContentBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_CreateText_Encoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var encoding = GetRandomEncoding();
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                StreamWriter sw = null;
+
+                try
+                {
+                    sw = client.CreateText(remoteFile, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    sw?.Dispose();
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_CreateText_Encoding_FileDoesNotExist()
+        {
+            var encoding = GetRandomEncoding();
+            var initialContent = "\u0100ert & Ann";
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (client.CreateText(remoteFile, encoding))
+                    {
+                    }
+
+                    // verify that file containing only preamble was created
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var file = client.GetAttributes(remoteFile);
+                    Assert.AreEqual(encoding.GetPreamble().Length, file.Size);
+
+                    client.DeleteFile(remoteFile);
+
+                    using (var sw = client.CreateText(remoteFile, encoding))
+                    {
+                        sw.Write(initialContent);
+                    }
+
+                    // verify that content is written to file
+                    var text = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(initialContent, text);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    { 
+                            client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_DownloadFile_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                using (var ms = new MemoryStream())
+                {
+                    try
+                    {
+                        client.DownloadFile(remoteFile, ms);
+                        Assert.Fail();
+                    }
+                    catch (SftpPathNotFoundException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual("No such file", ex.Message);
+
+                        // ensure file was not created by us
+                        Assert.IsFalse(client.Exists(remoteFile));
+                    }
+                    finally
+                    {
+                        if (client.Exists(remoteFile))
+                        {
+                            client.DeleteFile(remoteFile);
+                        }
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllBytes_ExistingFile()
+        {
+            var encoding = GetRandomEncoding();
+            var content = "\u0100ert & Ann";
+            var contentBytes = GetBytesWithPreamble(content, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, content, encoding);
+
+                    var actualBytes = client.ReadAllBytes(remoteFile);
+                    Assert.IsTrue(contentBytes.IsEqualTo(actualBytes));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    { 
+                            client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllBytes_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.ReadAllBytes(remoteFile);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure file was not created by us
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllLines_NoEncoding_ExistingFile()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" };
+            var linesBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, lines) + Environment.NewLine,
+                                                  encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile))
+                    {
+                        for (var i = 0; i < lines.Length; i++)
+                        {
+                            sw.WriteLine(lines[i]);
+                        }
+                    }
+
+                    var actualLines = client.ReadAllLines(remoteFile);
+                    Assert.IsNotNull(actualLines);
+                    Assert.AreEqual(lines.Length, actualLines.Length);
+
+                    for (var i = 0; i < lines.Length; i++)
+                    {
+                        Assert.AreEqual(lines[i], actualLines[i]);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(linesBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllLines_NoEncoding_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.ReadAllLines(remoteFile);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure file was not created by us
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllLines_Encoding_ExistingFile()
+        {
+            var encoding = GetRandomEncoding();
+            var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" };
+            var linesBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, lines) + Environment.NewLine,
+                                                  encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile, encoding))
+                    {
+                        for (var i = 0; i < lines.Length; i++)
+                        {
+                            sw.WriteLine(lines[i]);
+                        }
+                    }
+
+                    var actualLines = client.ReadAllLines(remoteFile, encoding);
+                    Assert.IsNotNull(actualLines);
+                    Assert.AreEqual(lines.Length, actualLines.Length);
+
+                    for (var i = 0; i < lines.Length; i++)
+                    {
+                        Assert.AreEqual(lines[i], actualLines[i]);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(linesBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllLines_Encoding_FileDoesNotExist()
+        {
+            var encoding = GetRandomEncoding();
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.ReadAllLines(remoteFile, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure file was not created by us
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllText_NoEncoding_ExistingFile()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" };
+            var expectedText = string.Join(Environment.NewLine, lines) + Environment.NewLine;
+            var expectedBytes = GetBytesWithPreamble(expectedText, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile))
+                    {
+                        for (var i = 0; i < lines.Length; i++)
+                        {
+                            sw.WriteLine(lines[i]);
+                        }
+                    }
+
+                    var actualText = client.ReadAllText(remoteFile);
+                    Assert.AreEqual(actualText, expectedText);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllText_NoEncoding_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.ReadAllText(remoteFile);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure file was not created by us
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllText_Encoding_ExistingFile()
+        {
+            var encoding = GetRandomEncoding();
+            var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" };
+            var expectedText = string.Join(Environment.NewLine, lines) + Environment.NewLine;
+            var expectedBytes = GetBytesWithPreamble(expectedText, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile, encoding))
+                    {
+                        for (var i = 0; i < lines.Length; i++)
+                        {
+                            sw.WriteLine(lines[i]);
+                        }
+                    }
+
+                    var actualText = client.ReadAllText(remoteFile, encoding);
+                    Assert.AreEqual(expectedText, actualText);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadAllText_Encoding_FileDoesNotExist()
+        {
+            var encoding = GetRandomEncoding();
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.ReadAllText(remoteFile, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure file was not created by us
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadLines_NoEncoding_ExistingFile()
+        {
+            var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile))
+                    {
+                        for (var i = 0; i < lines.Length; i++)
+                        {
+                            sw.WriteLine(lines[i]);
+                        }
+                    }
+
+                    var actualLines = client.ReadLines(remoteFile);
+                    Assert.IsNotNull(actualLines);
+
+                    var actualLinesEnum = actualLines.GetEnumerator();
+                    for (var i = 0; i < lines.Length; i++)
+                    {
+                        Assert.IsTrue(actualLinesEnum.MoveNext());
+                        var actualLine = actualLinesEnum.Current;
+                        Assert.AreEqual(lines[i], actualLine);
+                    }
+
+                    Assert.IsFalse(actualLinesEnum.MoveNext());
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadLines_NoEncoding_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.ReadLines(remoteFile);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure file was not created by us
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadLines_Encoding_ExistingFile()
+        {
+            var encoding = GetRandomEncoding();
+            var lines = new[] { "\u0100ert & Ann", "Forever", "&", "\u0116ver" };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var sw = client.AppendText(remoteFile, encoding))
+                    {
+                        for (var i = 0; i < lines.Length; i++)
+                        {
+                            sw.WriteLine(lines[i]);
+                        }
+                    }
+
+                    var actualLines = client.ReadLines(remoteFile, encoding);
+                    Assert.IsNotNull(actualLines);
+
+                    using (var actualLinesEnum = actualLines.GetEnumerator())
+                    {
+                        for (var i = 0; i < lines.Length; i++)
+                        {
+                            Assert.IsTrue(actualLinesEnum.MoveNext());
+
+                            var actualLine = actualLinesEnum.Current;
+                            Assert.AreEqual(lines[i], actualLine);
+                        }
+
+                        Assert.IsFalse(actualLinesEnum.MoveNext());
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ReadLines_Encoding_FileDoesNotExist()
+        {
+            var encoding = GetRandomEncoding();
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.ReadLines(remoteFile, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure file was not created by us
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllBytes_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var content = GenerateRandom(size: 5);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllBytes(remoteFile, content);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllBytes_ExistingFile()
+        {
+            var initialContent = GenerateRandom(size: 13);
+            var newContent1 = GenerateRandom(size: 5);
+            var expectedContent1 = new ArrayBuilder<byte>().Add(newContent1)
+                                                           .Add(initialContent, newContent1.Length, initialContent.Length - newContent1.Length)
+                                                           .Build();
+            var newContent2 = GenerateRandom(size: 50000);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var fs = client.Create(remoteFile))
+                    {
+                        fs.Write(initialContent, offset: 0, initialContent.Length);
+                    }
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    client.WriteAllBytes(remoteFile, newContent1);
+
+                    var actualContent1 = client.ReadAllBytes(remoteFile);
+                    Assert.IsTrue(expectedContent1.IsEqualTo(actualContent1));
+
+                    #endregion Write less bytes than the initial content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    client.WriteAllBytes(remoteFile, newContent2);
+
+                    var actualContent2 = client.ReadAllBytes(remoteFile);
+                    Assert.IsTrue(newContent2.IsEqualTo(actualContent2));
+
+                    #endregion Write less bytes than the initial content, overwriting part of that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllBytes_FileDoesNotExist()
+        {
+            var content = GenerateRandom(size: 13);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllBytes(remoteFile, content);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(content.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_IEnumerable_NoEncoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            IEnumerable<string> linesToWrite = new[] { "Forever", "&", "\u0116ver" };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_IEnumerable_NoEncoding_ExistingFile()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            var initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie";
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            IEnumerable<string> linesToWrite1 = new[] { "Forever", "&", "\u0116ver" };
+            var linesToWrite1Bytes =
+                GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding);
+            var expectedBytes1 = new ArrayBuilder<byte>().Add(linesToWrite1Bytes)
+                                                         .Add(initialContentBytes,
+                                                              linesToWrite1Bytes.Length,
+                                                              initialContentBytes.Length - linesToWrite1Bytes.Length)
+                                                         .Build();
+            IEnumerable<string> linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" };
+            var linesToWrite2Bytes =
+                GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding);
+            var expectedBytes2 = linesToWrite2Bytes;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    // create initial content
+                    client.WriteAllText(remoteFile, initialContent);
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite1);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write less bytes than the current content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite2);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write more bytes than the current content, overwriting and appending to that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_IEnumerable_NoEncoding_FileDoesNotExist()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            IEnumerable<string> linesToWrite = new[] { "\u0139isa", "&", "Sofie" };
+            var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_IEnumerable_Encoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var encoding = GetRandomEncoding();
+            IEnumerable<string> linesToWrite = new[] { "Forever", "&", "\u0116ver" };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_IEnumerable_Encoding_ExistingFile()
+        {
+            var encoding = GetRandomEncoding();
+            const string initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie";
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            IEnumerable<string> linesToWrite1 = new[] { "Forever", "&", "\u0116ver" };
+            var linesToWrite1Bytes =
+                GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding);
+            var expectedBytes1 = new ArrayBuilder<byte>().Add(linesToWrite1Bytes)
+                                                         .Add(initialContentBytes,
+                                                              linesToWrite1Bytes.Length,
+                                                              initialContentBytes.Length - linesToWrite1Bytes.Length)
+                                                         .Build();
+            IEnumerable<string> linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" };
+            var linesToWrite2Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding);
+            var expectedBytes2 = linesToWrite2Bytes;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    // create initial content
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite1, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write less bytes than the current content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite2, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write more bytes than the current content, overwriting and appending to that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_IEnumerable_Encoding_FileDoesNotExist()
+        {
+            var encoding = GetRandomEncoding();
+            IEnumerable<string> linesToWrite = new[] { "\u0139isa", "&", "Sofie" };
+            var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_Array_NoEncoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var linesToWrite = new[] { "Forever", "&", "\u0116ver" };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_Array_NoEncoding_ExistingFile()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            const string initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie";
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            var linesToWrite1 = new[] { "Forever", "&", "\u0116ver" };
+            var linesToWrite1Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding);
+            var expectedBytes1 = new ArrayBuilder<byte>().Add(linesToWrite1Bytes)
+                                                         .Add(initialContentBytes, linesToWrite1Bytes.Length, initialContentBytes.Length - linesToWrite1Bytes.Length)
+                                                         .Build();
+            var linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" };
+            var linesToWrite2Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding);
+            var expectedBytes2 = linesToWrite2Bytes;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    // create initial content
+                    client.WriteAllText(remoteFile, initialContent);
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite1);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write less bytes than the current content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite2);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write more bytes than the current content, overwriting and appending to that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_Array_NoEncoding_FileDoesNotExist()
+        {
+            var encoding = new UTF8Encoding(false, true);
+            var linesToWrite = new[] { "\u0139isa", "&", "Sofie" };
+            var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_Array_Encoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var encoding = GetRandomEncoding();
+            var linesToWrite = new[] { "Forever", "&", "\u0116ver" };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_Array_Encoding_ExistingFile()
+        {
+            const string initialContent = "\u0100ert & Ann Forever & Ever Lisa & Sofie";
+
+            var encoding = GetRandomEncoding();
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            var linesToWrite1 = new[] { "Forever", "&", "\u0116ver" };
+            var linesToWrite1Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite1) + Environment.NewLine, encoding);
+            var expectedBytes1 = new ArrayBuilder<byte>().Add(linesToWrite1Bytes)
+                                                         .Add(initialContentBytes, linesToWrite1Bytes.Length, initialContentBytes.Length - linesToWrite1Bytes.Length)
+                                                         .Build();
+            var linesToWrite2 = new[] { "Forever", "&", "\u0116ver", "Gert & Ann", "Lisa + Sofie" };
+            var linesToWrite2Bytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite2) + Environment.NewLine, encoding);
+            var expectedBytes2 = linesToWrite2Bytes;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    // create initial content
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite1, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write less bytes than the current content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    client.WriteAllLines(remoteFile, linesToWrite2, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write more bytes than the current content, overwriting and appending to that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllLines_Array_Encoding_FileDoesNotExist()
+        {
+            var encoding = GetRandomEncoding();
+            var linesToWrite = new[] { "\u0139isa", "&", "Sofie" };
+            var linesToWriteBytes = GetBytesWithPreamble(string.Join(Environment.NewLine, linesToWrite) + Environment.NewLine, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllLines(remoteFile, linesToWrite, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(linesToWriteBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllText_NoEncoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllText_NoEncoding_ExistingFile()
+        {
+            const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie";
+            const string newContent1 = "For\u0116ver & Ever";
+
+            var encoding = new UTF8Encoding(false, true);
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            var newContent1Bytes = GetBytesWithPreamble(newContent1, encoding);
+            var expectedBytes1 = new ArrayBuilder<byte>().Add(newContent1Bytes)
+                                                         .Add(initialContentBytes, newContent1Bytes.Length, initialContentBytes.Length - newContent1Bytes.Length)
+                                                         .Build();
+            var newContent2 = "Sofie & Lisa For\u0116ver & Ever with \u0100ert & Ann";
+            var newContent2Bytes = GetBytesWithPreamble(newContent2, encoding);
+            var expectedBytes2 = newContent2Bytes;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    client.WriteAllText(remoteFile, newContent1);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write less bytes than the current content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    client.WriteAllText(remoteFile, newContent2);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write more bytes than the current content, overwriting and appending to that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllText_NoEncoding_FileDoesNotExist()
+        {
+            const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie";
+
+            var encoding = new UTF8Encoding(false, true);
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllText_Encoding_DirectoryDoesNotExist()
+        {
+            const string remoteFile = "/home/sshnet/directorydoesnotexist/test";
+
+            var encoding = GetRandomEncoding();
+            const string content = "For\u0116ver & Ever";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, content, encoding);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllText_Encoding_ExistingFile()
+        {
+            const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie";
+            const string newContent1 = "For\u0116ver & Ever";
+            const string newContent2 = "Sofie & Lisa For\u0116ver & Ever with \u0100ert & Ann";
+
+            var encoding = GetRandomEncoding();
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+            var newContent1Bytes = GetBytesWithPreamble(newContent1, encoding);
+            var expectedBytes1 = new ArrayBuilder<byte>().Add(newContent1Bytes)
+                                                         .Add(initialContentBytes, newContent1Bytes.Length, initialContentBytes.Length - newContent1Bytes.Length)
+                                                         .Build();
+            var newContent2Bytes = GetBytesWithPreamble(newContent2, encoding);
+            var expectedBytes2 = newContent2Bytes;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+
+                    #region Write less bytes than the current content, overwriting part of that content
+
+                    client.WriteAllText(remoteFile, newContent1, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes1.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write less bytes than the current content, overwriting part of that content
+
+                    #region Write more bytes than the current content, overwriting and appending to that content
+
+                    client.WriteAllText(remoteFile, newContent2, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(expectedBytes2.IsEqualTo(actualBytes));
+                    }
+
+                    #endregion Write more bytes than the current content, overwriting and appending to that content
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_WriteAllText_Encoding_FileDoesNotExist()
+        {
+            const string initialContent = "\u0100ert & Ann Forever & \u0116ver Lisa & Sofie";
+
+            var encoding = GetRandomEncoding();
+            var initialContentBytes = GetBytesWithPreamble(initialContent, encoding);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, initialContent, encoding);
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var actualBytes = new byte[fs.Length];
+                        fs.Read(actualBytes, offset: 0, actualBytes.Length);
+                        Assert.IsTrue(initialContentBytes.IsEqualTo(actualBytes));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginDownloadFile_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var ms = new MemoryStream())
+                    {
+                        var asyncResult = client.BeginDownloadFile(remoteFile, ms);
+                        try
+                        {
+                            client.EndDownloadFile(asyncResult);
+                            Assert.Fail();
+                        }
+                        catch (SftpPathNotFoundException ex)
+                        {
+                            Assert.IsNull(ex.InnerException);
+                            Assert.AreEqual("No such file", ex.Message);
+
+                            // ensure file was not created by us
+                            Assert.IsFalse(client.Exists(remoteFile));
+                        }
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginListDirectory_DirectoryDoesNotExist()
+        {
+            const string remoteDirectory = "/home/sshnet/test123";
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+            }
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var asyncResult = client.BeginListDirectory(remoteDirectory, null, null);
+                try
+                {
+                    client.EndListDirectory(asyncResult);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException ex)
+                {
+                    Assert.IsNull(ex.InnerException);
+                    Assert.AreEqual("No such file", ex.Message);
+
+                    // ensure directory was not created by us
+                    Assert.IsFalse(client.Exists(remoteDirectory));
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPath_DirectoryDoesNotExist()
+        {
+            const int size = 50 * 1024 * 1024;
+            const string remoteDirectory = "/home/sshnet/test123";
+            const string remoteFile = remoteDirectory + "/test";
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+            }
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var memoryStream = CreateMemoryStream(size);
+                memoryStream.Position = 0;
+
+                var asyncResult = client.BeginUploadFile(memoryStream, remoteFile);
+                try
+                {
+                    client.EndUploadFile(asyncResult);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException)
+                {
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPath_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    var uploadMemoryStream = new MemoryStream();
+                    var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8);
+                    sw.Write("Gert & Ann");
+                    sw.Flush();
+                    uploadMemoryStream.Position = 0;
+
+                    var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile);
+                    client.EndUploadFile(asyncResult);
+
+                    using (var downloadMemoryStream = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloadMemoryStream);
+
+                        downloadMemoryStream.Position = 0;
+
+                        using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8))
+                        {
+                            var content = sr.ReadToEnd();
+                            Assert.AreEqual("Gert & Ann", content);
+                        }
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPath_ExistingFile()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, "Gert & Ann & Lisa");
+
+                    var uploadMemoryStream = new MemoryStream();
+                    var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8);
+                    sw.Write("Ann & Gert");
+                    sw.Flush();
+                    uploadMemoryStream.Position = 0;
+
+                    var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile);
+                    client.EndUploadFile(asyncResult);
+
+                    using (var downloadMemoryStream = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloadMemoryStream);
+
+                        downloadMemoryStream.Position = 0;
+
+                        using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8))
+                        {
+                            var content = sr.ReadToEnd();
+                            Assert.AreEqual("Ann & Gert", content);
+                        }
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsFalse_DirectoryDoesNotExist()
+        {
+            const int size = 50 * 1024 * 1024;
+            const string remoteDirectory = "/home/sshnet/test123";
+            const string remoteFile = remoteDirectory + "/test";
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+            }
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var memoryStream = CreateMemoryStream(size);
+                memoryStream.Position = 0;
+
+                var asyncResult = client.BeginUploadFile(memoryStream, remoteFile, false, null, null);
+                try
+                {
+                    client.EndUploadFile(asyncResult);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException)
+                {
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsFalse_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var uploadMemoryStream = new MemoryStream())
+                    using (var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8))
+                    {
+                        sw.Write("Gert & Ann");
+                        sw.Flush();
+                        uploadMemoryStream.Position = 0;
+
+                        var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, false, null, null);
+                        client.EndUploadFile(asyncResult);
+                    }
+
+                    using (var downloadMemoryStream = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloadMemoryStream);
+
+                        downloadMemoryStream.Position = 0;
+
+                        using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8))
+                        {
+                            var content = sr.ReadToEnd();
+                            Assert.AreEqual("Gert & Ann", content);
+                        }
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsFalse_ExistingFile()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, "Gert & Ann & Lisa");
+
+                    var uploadMemoryStream = new MemoryStream();
+                    var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8);
+                    sw.Write("Ann & Gert");
+                    sw.Flush();
+                    uploadMemoryStream.Position = 0;
+
+                    var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, false, null, null);
+
+                    try
+                    {
+                        client.EndUploadFile(asyncResult);
+                        Assert.Fail();
+                    }
+                    catch (SshException ex)
+                    {
+                        Assert.AreEqual(typeof(SshException), ex.GetType());
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual("Failure", ex.Message);
+                    }
+                }
+                finally
+                {
+
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsTrue_DirectoryDoesNotExist()
+        {
+            const int size = 50 * 1024 * 1024;
+            const string remoteDirectory = "/home/sshnet/test123";
+            const string remoteFile = remoteDirectory + "/test";
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+            }
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var memoryStream = CreateMemoryStream(size);
+                memoryStream.Position = 0;
+
+                var asyncResult = client.BeginUploadFile(memoryStream, remoteFile, true, null, null);
+                try
+                {
+                    client.EndUploadFile(asyncResult);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException)
+                {
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsTrue_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var uploadMemoryStream = new MemoryStream())
+                    using (var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8))
+                    {
+                        sw.Write("Gert & Ann");
+                        sw.Flush();
+                        uploadMemoryStream.Position = 0;
+
+                        var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, true, null, null);
+                        client.EndUploadFile(asyncResult);
+                    }
+
+                    using (var downloadMemoryStream = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloadMemoryStream);
+
+                        downloadMemoryStream.Position = 0;
+
+                        using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8))
+                        {
+                            var content = sr.ReadToEnd();
+                            Assert.AreEqual("Gert & Ann", content);
+                        }
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_BeginUploadFile_InputAndPathAndCanOverride_CanOverrideIsTrue_ExistingFile()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllText(remoteFile, "Gert & Ann & Lisa");
+
+                    using (var uploadMemoryStream = new MemoryStream())
+                    using (var sw = new StreamWriter(uploadMemoryStream, Encoding.UTF8))
+                    {
+                        sw.Write("Ann & Gert");
+                        sw.Flush();
+                        uploadMemoryStream.Position = 0;
+
+                        var asyncResult = client.BeginUploadFile(uploadMemoryStream, remoteFile, true, null, null);
+                        client.EndUploadFile(asyncResult);
+                    }
+
+                    using (var downloadMemoryStream = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloadMemoryStream);
+
+                        downloadMemoryStream.Position = 0;
+
+                        using (var sr = new StreamReader(downloadMemoryStream, Encoding.UTF8))
+                        {
+                            var content = sr.ReadToEnd();
+                            Assert.AreEqual("Ann & Gert", content);
+                        }
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_UploadAndDownloadBigFile()
+        {
+            const int size = 50 * 1024 * 1024;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.Delete(remoteFile);
+                }
+
+                try
+                {
+                    var memoryStream = CreateMemoryStream(size);
+                    memoryStream.Position = 0;
+
+                    client.UploadFile(memoryStream, remoteFile);
+
+                    var stopwatch = new Stopwatch();
+                    stopwatch.Start();
+
+                    // check uploaded file
+                    memoryStream = new MemoryStream();
+                    client.DownloadFile(remoteFile, memoryStream);
+
+                    Assert.AreEqual(size, memoryStream.Length);
+
+                    stopwatch.Stop();
+
+                    Console.WriteLine(@"Elapsed: {0} ms", stopwatch.ElapsedMilliseconds);
+                    Console.WriteLine(@"Transfer speed: {0:N2} KB/s",
+                                      CalculateTransferSpeed(memoryStream.Length, stopwatch.ElapsedMilliseconds));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.Delete(remoteFile);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Issue 1672
+        /// </summary>
+        [TestMethod]
+        public void Sftp_CurrentWorkingDirectory()
+        {
+            const string homeDirectory = "/home/sshnet";
+            const string otherDirectory = homeDirectory + "/dir";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(otherDirectory))
+                {
+                    client.DeleteDirectory(otherDirectory);
+                }
+
+                try
+                {
+                    client.CreateDirectory(otherDirectory);
+                    client.ChangeDirectory(otherDirectory);
+
+                    using (var s = CreateStreamWithContent("A"))
+                    {
+                        client.UploadFile(s, "a.txt");
+                    }
+
+                    using (var s = new MemoryStream())
+                    {
+                        client.DownloadFile("a.txt", s);
+                        s.Position = 0;
+
+                        var content = Encoding.ASCII.GetString(s.ToArray());
+                        Assert.AreEqual("A", content);
+                    }
+
+                    Assert.IsTrue(client.Exists(otherDirectory + "/a.txt"));
+                    client.DeleteFile("a.txt");
+                    Assert.IsFalse(client.Exists(otherDirectory + "/a.txt"));
+                    client.DeleteDirectory(".");
+                    Assert.IsFalse(client.Exists(otherDirectory));
+                }
+                finally
+                {
+                    if (client.Exists(otherDirectory))
+                    {
+                        client.DeleteDirectory(otherDirectory);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Exists()
+        {
+            const string remoteHome = "/home/sshnet";
+
+            #region Setup
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                #region Clean-up
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/DoesNotExist"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.directory.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/directory.exists"}")
+                )
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.file.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -f {remoteHome + "/file.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                #endregion Clean-up
+
+                #region Setup
+
+                using (var command = client.CreateCommand($"touch {remoteHome + "/file.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"mkdir {remoteHome + "/directory.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"ln -s {remoteHome + "/file.exists"} {remoteHome + "/symlink.to.file.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"ln -s {remoteHome + "/directory.exists"} {remoteHome + "/symlink.to.directory.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                #endregion Setup
+            }
+
+            #endregion Setup
+
+            #region Assert
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                Assert.IsFalse(client.Exists(remoteHome + "/DoesNotExist"));
+                Assert.IsTrue(client.Exists(remoteHome + "/file.exists"));
+                Assert.IsTrue(client.Exists(remoteHome + "/symlink.to.file.exists"));
+                Assert.IsTrue(client.Exists(remoteHome + "/directory.exists"));
+                Assert.IsTrue(client.Exists(remoteHome + "/symlink.to.directory.exists"));
+            }
+
+            #endregion Assert
+
+            #region Teardown
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/DoesNotExist"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.directory.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/directory.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -Rf {remoteHome + "/symlink.to.file.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+
+                using (var command = client.CreateCommand($"rm -f {remoteHome + "/file.exists"}"))
+                {
+                    command.Execute();
+                    Assert.AreEqual(0, command.ExitStatus, command.Error);
+                }
+            }
+
+            #endregion Teardown
+        }
+
+        [TestMethod]
+        public void Sftp_ListDirectory()
+        {
+            const string remoteDirectory = "/home/sshnet/test123";
+
+            try
+            {
+                using (var client = new SshClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+                    client.RunCommand($@"rm -Rf ""{remoteDirectory}""");
+                    client.RunCommand($@"mkdir -p ""{remoteDirectory}""");
+                    client.RunCommand($@"mkdir -p ""{remoteDirectory}/sub""");
+                    client.RunCommand($@"touch ""{remoteDirectory}/file1""");
+                    client.RunCommand($@"touch ""{remoteDirectory}/file2""");
+                }
+
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    client.ChangeDirectory(remoteDirectory);
+
+                    var directoryContent = client.ListDirectory(".").OrderBy(p => p.Name).ToList();
+                    Assert.AreEqual(5, directoryContent.Count);
+
+                    Assert.AreEqual(".", directoryContent[0].Name);
+                    Assert.AreEqual($"{remoteDirectory}/.", directoryContent[0].FullName);
+                    Assert.IsTrue(directoryContent[0].IsDirectory);
+
+                    Assert.AreEqual("..", directoryContent[1].Name);
+                    Assert.AreEqual($"{remoteDirectory}/..", directoryContent[1].FullName);
+                    Assert.IsTrue(directoryContent[1].IsDirectory);
+
+                    Assert.AreEqual("file1", directoryContent[2].Name);
+                    Assert.AreEqual($"{remoteDirectory}/file1", directoryContent[2].FullName);
+                    Assert.IsFalse(directoryContent[2].IsDirectory);
+
+                    Assert.AreEqual("file2", directoryContent[3].Name);
+                    Assert.AreEqual($"{remoteDirectory}/file2", directoryContent[3].FullName);
+                    Assert.IsFalse(directoryContent[3].IsDirectory);
+
+                    Assert.AreEqual("sub", directoryContent[4].Name);
+                    Assert.AreEqual($"{remoteDirectory}/sub", directoryContent[4].FullName);
+                    Assert.IsTrue(directoryContent[4].IsDirectory);
+                }
+            }
+            finally
+            {
+                using (var client = new SshClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                    {
+                        command.Execute();
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ChangeDirectory_DirectoryDoesNotExist()
+        {
+            const string remoteDirectory = "/home/sshnet/test123";
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+            }
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                try
+                {
+                    client.ChangeDirectory(remoteDirectory);
+                    Assert.Fail();
+                }
+                catch (SftpPathNotFoundException)
+                {
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_ChangeDirectory_DirectoryExists()
+        {
+            const string remoteDirectory = "/home/sshnet/test123";
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+
+                using (var command = client.CreateCommand("mkdir -p " + _remotePathTransformation.Transform(remoteDirectory)))
+                {
+                    command.Execute();
+                }
+            }
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    client.ChangeDirectory(remoteDirectory);
+
+                    Assert.AreEqual(remoteDirectory, client.WorkingDirectory);
+
+                    using (var uploadStream = CreateMemoryStream(100))
+                    {
+                        uploadStream.Position = 0;
+
+                        client.UploadFile(uploadStream, "gert.txt");
+
+                        uploadStream.Position = 0;
+
+                        using (var downloadStream = client.OpenRead(remoteDirectory + "/gert.txt"))
+                        {
+                            Assert.AreEqual(CreateHash(uploadStream), CreateHash(downloadStream));
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                using (var client = new SshClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    using (var command = client.CreateCommand("rm -Rf " + _remotePathTransformation.Transform(remoteDirectory)))
+                    {
+                        command.Execute();
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_DownloadFile_MemoryStream()
+        {
+            const int fileSize = 500 * 1024;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                SftpCreateRemoteFile(client, remoteFile, fileSize);
+
+                try
+                {
+                    using (var memoryStream = new MemoryStream())
+                    {
+                        var stopwatch = new Stopwatch();
+                        stopwatch.Start();
+
+                        client.DownloadFile(remoteFile, memoryStream);
+                        stopwatch.Stop();
+
+                        var transferSpeed = CalculateTransferSpeed(memoryStream.Length, stopwatch.ElapsedMilliseconds);
+                        Console.WriteLine(@"Elapsed: {0} ms", stopwatch.ElapsedMilliseconds);
+                        Console.WriteLine(@"Transfer speed: {0:N2} KB/s", transferSpeed);
+
+                        Assert.AreEqual(fileSize, memoryStream.Length);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SubsystemExecution_Failed()
+        {
+            var remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
+
+            // Disable SFTP subsystem
+            remoteSshdConfig.ClearSubsystems()
+                            .Update()
+                            .Restart();
+
+            var remoteSshdReconfiguredToDefaultState = false;
+
+            try
+            {
+                using (var client = new SftpClient(_connectionInfoFactory.Create()))
+                {
+                    try
+                    {
+                        client.Connect();
+                        Assert.Fail("Establishing SFTP connection should have failed.");
+                    }
+                    catch (SshException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual("Subsystem 'sftp' could not be executed.", ex.Message);
+                    }
+
+                    // Re-enable SFTP subsystem
+                    remoteSshdConfig.Reset();
+
+                    remoteSshdReconfiguredToDefaultState = true;
+
+                    // ensure we can reconnect the same SftpClient instance
+                    client.Connect();
+                    // ensure SFTP session is correctly established
+                    Assert.IsTrue(client.Exists("."));
+                }
+            }
+            finally
+            {
+                if (!remoteSshdReconfiguredToDefaultState)
+                {
+                    remoteSshdConfig.Reset();
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SftpFileStream_ReadAndWrite()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var s = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write))
+                    {
+                        s.Write(new byte[] { 5, 4, 3, 2, 1 }, 1, 3);
+                    }
+
+                    // switch from read to write mode
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite))
+                    {
+                        Assert.AreEqual(4, s.ReadByte());
+                        Assert.AreEqual(3, s.ReadByte());
+
+                        Assert.AreEqual(2, s.Position);
+
+                        s.WriteByte(7);
+                        s.Write(new byte[] { 8, 9, 10, 11, 12 }, 1, 3);
+
+                        Assert.AreEqual(6, s.Position);
+                    }
+
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Read))
+                    {
+                        Assert.AreEqual(6, s.Length);
+
+                        var buffer = new byte[s.Length];
+                        Assert.AreEqual(6, s.Read(buffer, offset: 0, buffer.Length));
+
+                        CollectionAssert.AreEqual(new byte[] { 4, 3, 7, 9, 10, 11 }, buffer);
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, s.ReadByte());
+                    }
+
+                    // switch from read to write mode, and back to read mode and finally
+                    // append a byte
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite))
+                    {
+                        Assert.AreEqual(4, s.ReadByte());
+                        Assert.AreEqual(3, s.ReadByte());
+                        Assert.AreEqual(7, s.ReadByte());
+
+                        s.Write(new byte[] { 0, 1, 6, 4 }, 1, 2);
+
+                        Assert.AreEqual(11, s.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, s.ReadByte());
+
+                        s.WriteByte(12);
+                    }
+
+                    // switch from write to read mode, and back to write mode
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite))
+                    {
+                        s.WriteByte(5);
+                        Assert.AreEqual(3, s.ReadByte());
+                        s.WriteByte(13);
+
+                        Assert.AreEqual(3, s.Position);
+                    }
+
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Read))
+                    {
+                        Assert.AreEqual(7, s.Length);
+
+                        var buffer = new byte[s.Length];
+                        Assert.AreEqual(7, s.Read(buffer, offset: 0, buffer.Length));
+
+                        CollectionAssert.AreEqual(new byte[] { 5, 3, 13, 1, 6, 11, 12 }, buffer);
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, s.ReadByte());
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SftpFileStream_SetLength_ReduceLength()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var s = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write))
+                    {
+                        s.Write(new byte[] { 5, 4, 3, 2, 1 }, 1, 3);
+                    }
+
+                    // reduce length while in write mode, with data in write buffer, and before
+                    // current position
+                    using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write))
+                    {
+                        s.Position = 3;
+                        s.Write(new byte[] { 6, 7, 8, 9 }, offset: 0, count: 4);
+
+                        Assert.AreEqual(7, s.Position);
+
+                        // verify buffer has not yet been flushed
+                        using (var fs = client.Open(remoteFile, FileMode.Open, FileAccess.Read))
+                        {
+                            Assert.AreEqual(4, fs.ReadByte());
+                            Assert.AreEqual(3, fs.ReadByte());
+                            Assert.AreEqual(2, fs.ReadByte());
+
+                            // Ensure we've reached end of the stream
+                            Assert.AreEqual(-1, fs.ReadByte());
+                        }
+
+                        s.SetLength(5);
+
+                        Assert.AreEqual(5, s.Position);
+
+                        // verify that buffer was flushed and size has been modified
+                        using (var fs = client.Open(remoteFile, FileMode.Open, FileAccess.Read))
+                        {
+                            Assert.AreEqual(4, fs.ReadByte());
+                            Assert.AreEqual(3, fs.ReadByte());
+                            Assert.AreEqual(2, fs.ReadByte());
+                            Assert.AreEqual(6, fs.ReadByte());
+                            Assert.AreEqual(7, fs.ReadByte());
+
+                            // Ensure we've reached end of the stream
+                            Assert.AreEqual(-1, fs.ReadByte());
+                        }
+
+                        s.WriteByte(1);
+                    }
+
+                    // verify that last byte was correctly written to the file
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Read))
+                    {
+                        Assert.AreEqual(6, s.Length);
+
+                        var buffer = new byte[s.Length + 2];
+                        Assert.AreEqual(6, s.Read(buffer, offset: 0, buffer.Length));
+
+                        CollectionAssert.AreEqual(new byte[] { 4, 3, 2, 6, 7, 1, 0, 0 }, buffer);
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, s.ReadByte());
+                    }
+
+                    // reduce length while in read mode, but beyond current position
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite))
+                    {
+                        var buffer = new byte[1];
+                        Assert.AreEqual(1, s.Read(buffer, offset: 0, buffer.Length));
+
+                        CollectionAssert.AreEqual(new byte[] { 4 }, buffer);
+
+                        s.SetLength(3);
+
+                        using (var w = client.Open(remoteFile, FileMode.Open, FileAccess.Write))
+                        {
+                            w.Write(new byte[] { 8, 1, 6, 2 }, offset: 0, count: 4);
+                        }
+
+                        // verify that position was not changed
+                        Assert.AreEqual(1, s.Position);
+
+                        // verify that read buffer was cleared
+                        Assert.AreEqual(1, s.ReadByte());
+                        Assert.AreEqual(6, s.ReadByte());
+                        Assert.AreEqual(2, s.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, s.ReadByte());
+
+                        Assert.AreEqual(4, s.Length);
+                    }
+
+                    // reduce length while in read mode, but before current position
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.ReadWrite))
+                    {
+                        var buffer = new byte[4];
+                        Assert.AreEqual(4, s.Read(buffer, offset: 0, buffer.Length));
+
+                        CollectionAssert.AreEqual(new byte[] { 8, 1, 6, 2 }, buffer);
+
+                        Assert.AreEqual(4, s.Position);
+
+                        s.SetLength(3);
+
+                        // verify that position was moved to last byte
+                        Assert.AreEqual(3, s.Position);
+
+                        using (var w = client.Open(remoteFile, FileMode.Open, FileAccess.Read))
+                        {
+                            Assert.AreEqual(3, w.Length);
+
+                            Assert.AreEqual(8, w.ReadByte());
+                            Assert.AreEqual(1, w.ReadByte());
+                            Assert.AreEqual(6, w.ReadByte());
+
+                            // Ensure we've reached end of the stream
+                            Assert.AreEqual(-1, w.ReadByte());
+                        }
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, s.ReadByte());
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SftpFileStream_Seek_BeyondEndOfFile_SeekOriginBegin()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.BufferSize = 500;
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF but not beyond buffer size
+                    // do not write anything
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: 3L, SeekOrigin.Begin);
+
+                        Assert.AreEqual(3, newPosition);
+                        Assert.AreEqual(3, fs.Position);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF and beyond buffer size
+                    // do not write anything
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: 700L, SeekOrigin.Begin);
+
+                        Assert.AreEqual(700, newPosition);
+                        Assert.AreEqual(700, fs.Position);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF but not beyond buffer size
+                    // write less bytes than buffer size
+                    var seekOffset = 3L;
+
+                    // buffer holding the data that we'll write to the file
+                    var writeBuffer = GenerateRandom(size: 7);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin);
+
+                        Assert.AreEqual(seekOffset, newPosition);
+                        Assert.AreEqual(seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBufferffer = new byte[seekOffset - 1];
+                        Assert.AreEqual(soughtOverReadBufferffer.Length, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBufferffer.Length].IsEqualTo(soughtOverReadBufferffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF and beyond buffer size
+                    // write less bytes than buffer size
+                    seekOffset = 700L;
+
+                    // buffer holding the data that we'll write to the file
+                    writeBuffer = GenerateRandom(size: 4);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin);
+
+                        Assert.AreEqual(seekOffset, newPosition);
+                        Assert.AreEqual(seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBufferffer = new byte[seekOffset - 1];
+                        Assert.AreEqual(soughtOverReadBufferffer.Length, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBufferffer.Length].IsEqualTo(soughtOverReadBufferffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF but not beyond buffer size
+                    // write more bytes than buffer size
+                    writeBuffer = GenerateRandom(size: 600);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: 3L, SeekOrigin.Begin);
+
+                        Assert.AreEqual(3, newPosition);
+                        Assert.AreEqual(3, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(3 + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+                        Assert.AreEqual(0x00, fs.ReadByte());
+                        Assert.AreEqual(0x00, fs.ReadByte());
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF and beyond buffer size
+                    // write more bytes than buffer size
+                    writeBuffer = GenerateRandom(size: 600);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: 550, SeekOrigin.Begin);
+
+                        Assert.AreEqual(550, newPosition);
+                        Assert.AreEqual(550, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(550 + writeBuffer.Length, fs.Length);
+
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBuffer = new byte[550 - 1];
+                        Assert.AreEqual(550 - 1, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
+                        Assert.IsTrue(new byte[550 - 1].IsEqualTo(soughtOverReadBuffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SftpFileStream_Seek_BeyondEndOfFile_SeekOriginEnd()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.BufferSize = 500;
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF but not beyond buffer size
+                    // do not write anything
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: 3L, SeekOrigin.End);
+
+                        Assert.AreEqual(4, newPosition);
+                        Assert.AreEqual(4, fs.Position);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF and beyond buffer size
+                    // do not write anything
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: 700L, SeekOrigin.End);
+
+                        Assert.AreEqual(701, newPosition);
+                        Assert.AreEqual(701, fs.Position);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF but not beyond buffer size
+                    // write less bytes than buffer size
+                    var seekOffset = 3L;
+
+                    // buffer holding the data that we'll write to the file
+                    var writeBuffer = GenerateRandom(size: 7);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.End);
+
+                        Assert.AreEqual(4, newPosition);
+                        Assert.AreEqual(4, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBuffer = new byte[seekOffset];
+                        Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF and beyond buffer size
+                    // write less bytes than buffer size
+                    seekOffset = 700L;
+
+                    // buffer holding the data that we'll write to the file
+                    writeBuffer = GenerateRandom(size: 4);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.End);
+
+                        Assert.AreEqual(1 + seekOffset, newPosition);
+                        Assert.AreEqual(1 + seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBuffer = new byte[seekOffset];
+                        Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF but not beyond buffer size
+                    // write more bytes than buffer size
+                    seekOffset = 3L;
+                    writeBuffer = GenerateRandom(size: 600);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.End);
+
+                        Assert.AreEqual(1 + seekOffset, newPosition);
+                        Assert.AreEqual(1 + seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBuffer = new byte[seekOffset];
+                        Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    // seek beyond EOF and beyond buffer size
+                    // write more bytes than buffer size
+                    seekOffset = 550L;
+                    writeBuffer = GenerateRandom(size: 600);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.End);
+
+                        Assert.AreEqual(1 + seekOffset, newPosition);
+                        Assert.AreEqual(1 + seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1 + seekOffset + writeBuffer.Length, fs.Length);
+
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBuffer = new byte[seekOffset];
+                        Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SftpFileStream_Seek_NegativeOffSet_SeekOriginEnd()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.BufferSize = 500;
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                        fs.WriteByte(0x07);
+                        fs.WriteByte(0x05);
+                    }
+
+                    // seek within file and not beyond buffer size
+                    // do not write anything
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: -2L, SeekOrigin.End);
+
+                        Assert.AreEqual(1, newPosition);
+                        Assert.AreEqual(1, fs.Position);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(3, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+                        Assert.AreEqual(0x07, fs.ReadByte());
+                        Assert.AreEqual(0x05, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // buffer holding the data that we'll write to the file
+                    var writeBuffer = GenerateRandom(size: (int) client.BufferSize + 200);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    // seek within EOF and beyond buffer size
+                    // do not write anything
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: -100L, SeekOrigin.End);
+
+                        Assert.AreEqual(600, newPosition);
+                        Assert.AreEqual(600, fs.Position);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(writeBuffer.Length, fs.Length);
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // seek within EOF and within buffer size
+                    // write less bytes than buffer size
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+
+                        var newPosition = fs.Seek(offset: -3, SeekOrigin.End);
+
+                        Assert.AreEqual(697, newPosition);
+                        Assert.AreEqual(697, fs.Position);
+
+                        fs.WriteByte(0x01);
+                        fs.WriteByte(0x05);
+                        fs.WriteByte(0x04);
+                        fs.WriteByte(0x07);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(writeBuffer.Length + 1, fs.Length);
+
+                        var readBuffer = new byte[writeBuffer.Length - 3];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Take(readBuffer.Length)));
+
+                        Assert.AreEqual(0x01, fs.ReadByte());
+                        Assert.AreEqual(0x05, fs.ReadByte());
+                        Assert.AreEqual(0x04, fs.ReadByte());
+                        Assert.AreEqual(0x07, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    // buffer holding the data that we'll write to the file
+                    writeBuffer = GenerateRandom(size: (int) client.BufferSize * 4);
+
+                    // seek within EOF and beyond buffer size
+                    // write less bytes than buffer size
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+
+                        var newPosition = fs.Seek(offset: -(client.BufferSize * 2), SeekOrigin.End);
+
+                        Assert.AreEqual(1000, newPosition);
+                        Assert.AreEqual(1000, fs.Position);
+
+                        fs.WriteByte(0x01);
+                        fs.WriteByte(0x05);
+                        fs.WriteByte(0x04);
+                        fs.WriteByte(0x07);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(writeBuffer.Length, fs.Length);
+
+                        // First part of file should not have been touched
+                        var readBuffer = new byte[(int) client.BufferSize * 2];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Take(readBuffer.Length)));
+
+                        // Check part that should have been updated
+                        Assert.AreEqual(0x01, fs.ReadByte());
+                        Assert.AreEqual(0x05, fs.ReadByte());
+                        Assert.AreEqual(0x04, fs.ReadByte());
+                        Assert.AreEqual(0x07, fs.ReadByte());
+
+                        // Remaining bytes should not have been touched
+                        readBuffer = new byte[((int) client.BufferSize * 2) - 4];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Skip(((int)client.BufferSize * 2) + 4).Take(readBuffer.Length)));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        /// https://github.com/sshnet/SSH.NET/issues/253
+        [TestMethod]
+        public void Sftp_SftpFileStream_Seek_Issue253()
+        {
+            var buf = Encoding.UTF8.GetBytes("123456");
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var ws = client.OpenWrite(remoteFile))
+                    {
+                        ws.Write(buf, offset: 0, count: 3);
+                    }
+
+                    using (var ws = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = ws.Seek(offset: 3, SeekOrigin.Begin);
+
+                        Assert.AreEqual(3, newPosition);
+                        Assert.AreEqual(3, ws.Position);
+
+                        ws.Write(buf, 3, 3);
+                    }
+
+                    var actual = client.ReadAllText(remoteFile, Encoding.UTF8);
+                    Assert.AreEqual("123456", actual);
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SftpFileStream_Seek_WithinReadBuffer()
+        {
+            var originalContent = GenerateRandom(size: 800);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.BufferSize = 500;
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.Write(originalContent, offset: 0, originalContent.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        var readBuffer = new byte[200];
+
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+
+                        var newPosition = fs.Seek(offset: 3L, SeekOrigin.Begin);
+
+                        Assert.AreEqual(3L, newPosition);
+                        Assert.AreEqual(3L, fs.Position);
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    #region Seek beyond EOF and beyond buffer size do not write anything
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(offset: 700L, SeekOrigin.Begin);
+
+                        Assert.AreEqual(700L, newPosition);
+                        Assert.AreEqual(700L, fs.Position);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(1, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    #endregion Seek beyond EOF and beyond buffer size do not write anything
+
+                    #region Seek beyond EOF but not beyond buffer size and write less bytes than buffer size
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    var seekOffset = 3L;
+                    var writeBuffer = GenerateRandom(size: 7);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin);
+
+                        Assert.AreEqual(seekOffset, newPosition);
+                        Assert.AreEqual(seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBuffer = new byte[seekOffset - 1];
+                        Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    #endregion Seek beyond EOF but not beyond buffer size and write less bytes than buffer size
+
+                    #region Seek beyond EOF and beyond buffer size and write less bytes than buffer size
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    seekOffset = 700L;
+                    writeBuffer = GenerateRandom(size: 4);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin);
+
+                        Assert.AreEqual(seekOffset, newPosition);
+                        Assert.AreEqual(seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBufferffer = new byte[seekOffset - 1];
+                        Assert.AreEqual(soughtOverReadBufferffer.Length, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length));
+                        Assert.IsTrue(new byte[soughtOverReadBufferffer.Length].IsEqualTo(soughtOverReadBufferffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    #endregion Seek beyond EOF and beyond buffer size and write less bytes than buffer size
+
+                    #region Seek beyond EOF but not beyond buffer size and write more bytes than buffer size
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    seekOffset = 3L;
+                    writeBuffer = GenerateRandom(size: 600);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin);
+
+                        Assert.AreEqual(seekOffset, newPosition);
+                        Assert.AreEqual(seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length);
+                        Assert.AreEqual(0x04, fs.ReadByte());
+                        Assert.AreEqual(0x00, fs.ReadByte());
+                        Assert.AreEqual(0x00, fs.ReadByte());
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    #endregion Seek beyond EOF but not beyond buffer size and write more bytes than buffer size
+
+                    #region Seek beyond EOF and beyond buffer size and write more bytes than buffer size
+
+                    // create single-byte file
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        fs.WriteByte(0x04);
+                    }
+
+                    seekOffset = 550L;
+                    writeBuffer = GenerateRandom(size: 600);
+
+                    using (var fs = client.OpenWrite(remoteFile))
+                    {
+                        var newPosition = fs.Seek(seekOffset, SeekOrigin.Begin);
+
+                        Assert.AreEqual(seekOffset, newPosition);
+                        Assert.AreEqual(seekOffset, fs.Position);
+
+                        fs.Write(writeBuffer, offset: 0, writeBuffer.Length);
+                    }
+
+                    using (var fs = client.OpenRead(remoteFile))
+                    {
+                        Assert.AreEqual(seekOffset + writeBuffer.Length, fs.Length);
+
+                        Assert.AreEqual(0x04, fs.ReadByte());
+
+                        var soughtOverReadBufferffer = new byte[seekOffset - 1];
+                        Assert.AreEqual(seekOffset - 1, fs.Read(soughtOverReadBufferffer, offset: 0, soughtOverReadBufferffer.Length));
+                        Assert.IsTrue(new byte[seekOffset - 1].IsEqualTo(soughtOverReadBufferffer));
+
+                        var readBuffer = new byte[writeBuffer.Length];
+                        Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
+                        Assert.IsTrue(writeBuffer.IsEqualTo(readBuffer));
+
+                        // Ensure we've reached end of the stream
+                        Assert.AreEqual(-1, fs.ReadByte());
+                    }
+
+                    client.DeleteFile(remoteFile);
+
+                    #endregion Seek beyond EOF and beyond buffer size and write more bytes than buffer size
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SftpFileStream_SetLength_FileDoesNotExist()
+        {
+            var size = new Random().Next(500, 5000);
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write))
+                    {
+                        s.SetLength(size);
+                    }
+
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var attributes = client.GetAttributes(remoteFile);
+                    Assert.IsTrue(attributes.IsRegularFile);
+                    Assert.AreEqual(size, attributes.Size);
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(new byte[size]), CreateHash(downloaded));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_Append_Write_ExistingFile()
+        {
+            const int fileSize = 5 * 1024;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            using (var input = CreateMemoryStream(fileSize))
+            {
+                input.Position = 0;
+
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.UploadFile(input, remoteFile);
+
+                    using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write))
+                    {
+                        var buffer = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 };
+                        s.Write(buffer, offset: 0, buffer.Length);
+                        input.Write(buffer, offset: 0, buffer.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+
+                        input.Position = 0;
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(input), CreateHash(downloaded));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_Append_Write_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    #region Verify if merely opening the file for append creates a zero-byte file
+
+                    using (client.Open(remoteFile, FileMode.Append, FileAccess.Write))
+                    {
+                    }
+
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var attributes = client.GetAttributes(remoteFile);
+                    Assert.IsTrue(attributes.IsRegularFile);
+                    Assert.AreEqual(0L, attributes.Size);
+
+                    #endregion Verify if merely opening the file for append creates it
+
+                    client.DeleteFile(remoteFile);
+
+                    #region Verify if content is actually written to the file
+
+                    var content = GenerateRandom(size: 100);
+
+                    using (var s = client.Open(remoteFile, FileMode.Append, FileAccess.Write))
+                    {
+                        s.Write(content, offset: 0, content.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(content), CreateHash(downloaded));
+                    }
+
+                    #endregion Verify if content is actually written to the file
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Sftp_Open_PathAndMode_ModeIsCreate_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    #region Verify if merely opening the file for create creates a zero-byte file
+
+                    using (client.Open(remoteFile, FileMode.Create))
+                    {
+                    }
+
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var attributes = client.GetAttributes(remoteFile);
+                    Assert.IsTrue(attributes.IsRegularFile);
+                    Assert.AreEqual(0L, attributes.Size);
+
+                    #endregion Verify if merely opening the file for create creates a zero-byte file
+
+                    client.DeleteFile(remoteFile);
+
+                    #region Verify if content is actually written to the file
+
+                    var content = GenerateRandom(size: 100);
+
+                    using (var s = client.Open(remoteFile, FileMode.Create))
+                    {
+                        s.Write(content, offset: 0, content.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(content), CreateHash(downloaded));
+                    }
+
+                    #endregion Verify if content is actually written to the file
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_PathAndMode_ModeIsCreate_ExistingFile()
+        {
+            const int fileSize = 5 * 1024;
+            var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            using (var input = CreateMemoryStream(fileSize))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    input.Position = 0;
+                    client.UploadFile(input, remoteFile);
+
+                    using (var stream = client.Open(remoteFile, FileMode.Create))
+                    {
+                        // Verify if merely opening the file for create overwrites the file
+                        var attributes = client.GetAttributes(remoteFile);
+                        Assert.IsTrue(attributes.IsRegularFile);
+                        Assert.AreEqual(0L, attributes.Size);
+
+                        stream.Write(newContent, offset: 0, newContent.Length);
+                        stream.Position = 0;
+
+                        Assert.AreEqual(CreateHash(newContent), CreateHash(stream));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsReadWrite_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    #region Verify if merely opening the file for create creates a zero-byte file
+
+                    using (client.Open(remoteFile, FileMode.Create, FileAccess.ReadWrite))
+                    {
+                    }
+
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var attributes = client.GetAttributes(remoteFile);
+                    Assert.IsTrue(attributes.IsRegularFile);
+                    Assert.AreEqual(0L, attributes.Size);
+
+                    #endregion Verify if merely opening the file for create creates a zero-byte file
+
+                    client.DeleteFile(remoteFile);
+
+                    #region Verify if content is actually written to the file
+
+                    var content = GenerateRandom(size: 100);
+
+                    using (var s = client.Open(remoteFile, FileMode.Create, FileAccess.ReadWrite))
+                    {
+                        s.Write(content, offset: 0, content.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(content), CreateHash(downloaded));
+                    }
+
+                    #endregion Verify if content is actually written to the file
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsReadWrite_ExistingFile()
+        {
+            const int fileSize = 5 * 1024;
+            var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            using (var input = CreateMemoryStream(fileSize))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    input.Position = 0;
+                    client.UploadFile(input, remoteFile);
+
+                    using (var stream = client.Open(remoteFile, FileMode.Create, FileAccess.ReadWrite))
+                    {
+                        // Verify if merely opening the file for create overwrites the file
+                        var attributes = client.GetAttributes(remoteFile);
+                        Assert.IsTrue(attributes.IsRegularFile);
+                        Assert.AreEqual(0L, attributes.Size);
+
+                        stream.Write(newContent, offset: 0, newContent.Length);
+                        stream.Position = 0;
+
+                        Assert.AreEqual(CreateHash(newContent), CreateHash(stream));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsWrite_ExistingFile()
+        {
+            // use new content that contains less bytes than original content to
+            // verify whether file is first truncated
+            var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 };
+            var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllBytes(remoteFile, originalContent);
+
+                    using (var s = client.Open(remoteFile, FileMode.Create, FileAccess.Write))
+                    {
+                        s.Write(newContent, offset: 0, newContent.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(newContent), CreateHash(downloaded));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_PathAndModeAndAccess_ModeIsCreate_AccessIsWrite_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    #region Verify if merely opening the file for create creates a zero-byte file
+
+                    using (client.Open(remoteFile, FileMode.Create, FileAccess.Write))
+                    {
+                    }
+
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var attributes = client.GetAttributes(remoteFile);
+                    Assert.IsTrue(attributes.IsRegularFile);
+                    Assert.AreEqual(0L, attributes.Size);
+
+                    #endregion Verify if merely opening the file for create creates a zero-byte file
+
+                    client.DeleteFile(remoteFile);
+
+                    #region Verify if content is actually written to the file
+
+                    var content = GenerateRandom(size: 100);
+
+                    using (var s = client.Open(remoteFile, FileMode.Create, FileAccess.Write))
+                    {
+                        s.Write(content, offset: 0, content.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(content), CreateHash(downloaded));
+                    }
+
+                    #endregion Verify if content is actually written to the file
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_CreateNew_Write_ExistingFile()
+        {
+            const int fileSize = 5 * 1024;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            using (var input = CreateMemoryStream(fileSize))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                input.Position = 0;
+
+                try
+                {
+                    client.UploadFile(input, remoteFile);
+
+                    Stream stream = null;
+
+                    try
+                    {
+                        stream = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write);
+                        Assert.Fail();
+                    }
+                    catch (SshException)
+                    {
+                    }
+                    finally
+                    {
+                        stream?.Dispose();
+                    }
+
+                    // Verify that the file was not modified
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+
+                        input.Position = 0;
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(input), CreateHash(downloaded));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_CreateNew_Write_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    #region Verify if merely opening the file creates a zero-byte file
+
+                    using (client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write))
+                    {
+                    }
+
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var attributes = client.GetAttributes(remoteFile);
+                    Assert.IsTrue(attributes.IsRegularFile);
+                    Assert.AreEqual(0L, attributes.Size);
+
+                    #endregion Verify if merely opening the file creates it
+
+                    client.DeleteFile(remoteFile);
+
+                    #region Verify if content is actually written to the file
+
+                    var content = GenerateRandom(size: 100);
+
+                    using (var s = client.Open(remoteFile, FileMode.CreateNew, FileAccess.Write))
+                    {
+                        s.Write(content, offset: 0, content.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(content), CreateHash(downloaded));
+                    }
+
+                    #endregion Verify if content is actually written to the file
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_Open_Write_ExistingFile()
+        {
+            // use new content that contains less bytes than original content to
+            // verify whether file is first truncated
+            var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 };
+            var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b };
+            var expectedContent = new byte[] { 0x07, 0x03, 0x02, 0x0b, 0x04 };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllBytes(remoteFile, originalContent);
+
+                    using (var s = client.Open(remoteFile, FileMode.Open, FileAccess.Write))
+                    {
+                        s.Write(newContent, offset: 0, newContent.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(expectedContent), CreateHash(downloaded));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_Open_Write_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    Stream stream = null;
+
+                    try
+                    {
+                        stream = client.Open(remoteFile, FileMode.Open, FileAccess.Write);
+                        Assert.Fail();
+                    }
+                    catch (SshException)
+                    {
+                    }
+                    finally
+                    {
+                        stream?.Dispose();
+                    }
+
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_OpenOrCreate_Write_ExistingFile()
+        {
+            // use new content that contains less bytes than original content to
+            // verify whether file is first truncated
+            var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 };
+            var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b };
+            var expectedContent = new byte[] { 0x07, 0x03, 0x02, 0x0b, 0x04 };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    client.WriteAllBytes(remoteFile, originalContent);
+
+                    using (var s = client.Open(remoteFile, FileMode.OpenOrCreate, FileAccess.Write))
+                    {
+                        s.Write(newContent, offset: 0, newContent.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(expectedContent), CreateHash(downloaded));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_OpenOrCreate_Write_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    #region Verify if merely opening the file creates a zero-byte file
+
+                    using (client.Open(remoteFile, FileMode.OpenOrCreate, FileAccess.Write))
+                    {
+                    }
+
+                    Assert.IsTrue(client.Exists(remoteFile));
+
+                    var attributes = client.GetAttributes(remoteFile);
+                    Assert.IsTrue(attributes.IsRegularFile);
+                    Assert.AreEqual(0L, attributes.Size);
+
+                    #endregion Verify if merely opening the file creates it
+
+                    client.DeleteFile(remoteFile);
+
+                    #region Verify if content is actually written to the file
+
+                    var content = GenerateRandom(size: 100);
+
+                    using (var s = client.Open(remoteFile, FileMode.OpenOrCreate, FileAccess.Write))
+                    {
+                        s.Write(content, offset: 0, content.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(content), CreateHash(downloaded));
+                    }
+
+                    #endregion Verify if content is actually written to the file
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+
+
+
+
+
+
+        [TestMethod]
+        public void Sftp_Open_Truncate_Write_ExistingFile()
+        {
+            const int fileSize = 5 * 1024;
+
+            // use new content that contains less bytes than original content to
+            // verify whether file is first truncated
+            var originalContent = new byte[] { 0x05, 0x0f, 0x0d, 0x0a, 0x04 };
+            var newContent = new byte[] { 0x07, 0x03, 0x02, 0x0b };
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            using (var input = CreateMemoryStream(fileSize))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                input.Position = 0;
+
+                try
+                {
+                    client.WriteAllBytes(remoteFile, originalContent);
+
+                    using (var s = client.Open(remoteFile, FileMode.Truncate, FileAccess.Write))
+                    {
+                        s.Write(newContent, offset: 0, newContent.Length);
+                    }
+
+                    using (var downloaded = new MemoryStream())
+                    {
+                        client.DownloadFile(remoteFile, downloaded);
+
+                        input.Position = 0;
+                        downloaded.Position = 0;
+                        Assert.AreEqual(CreateHash(newContent), CreateHash(downloaded));
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_Open_Truncate_Write_FileDoesNotExist()
+        {
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                try
+                {
+                    Stream stream = null;
+
+                    try
+                    {
+                        stream = client.Open(remoteFile, FileMode.Truncate, FileAccess.Write);
+                        Assert.Fail();
+                    }
+                    catch (SshException)
+                    {
+                    }
+
+                    Assert.IsFalse(client.Exists(remoteFile));
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_OpenRead()
+        {
+            const int fileSize = 5 * 1024 * 1024;
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var remoteFile = GenerateUniqueRemoteFileName();
+
+                SftpCreateRemoteFile(client, remoteFile, fileSize);
+
+                try
+                {
+                    using (var s = client.OpenRead(remoteFile))
+                    {
+                        var buffer = new byte[s.Length];
+
+                        var stopwatch = new Stopwatch();
+                        stopwatch.Start();
+
+                        var bytesRead = s.Read(buffer, offset: 0, buffer.Length);
+
+                        stopwatch.Stop();
+
+                        var transferSpeed = CalculateTransferSpeed(bytesRead, stopwatch.ElapsedMilliseconds);
+                        Console.WriteLine(@"Elapsed: {0} ms", stopwatch.ElapsedMilliseconds);
+                        Console.WriteLine(@"Transfer speed: {0:N2} KB/s", transferSpeed);
+
+                        Assert.AreEqual(fileSize, bytesRead);
+                    }
+                }
+                finally
+                {
+                    if (client.Exists(remoteFile))
+                    {
+                        client.DeleteFile(remoteFile);
+                    }
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SetLastAccessTime()
+        {
+            var testFilePath = "/home/sshnet/test-file.txt";
+            var testContent = "File";
+            using var client = new SftpClient(_connectionInfoFactory.Create());
+            client.Connect();
+
+            using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+            var currentTime = DateTime.Now;
+
+            client.UploadFile(fileStream, testFilePath);
+
+            try
+            {
+                var time = client.GetLastAccessTime(testFilePath);
+
+                DateTimeAssert.AreEqual(currentTime.TruncateToWholeSeconds(), time);
+
+                var newTime = new DateTime(1986, 03, 15, 01, 02, 03, 123, DateTimeKind.Local);
+
+                client.SetLastAccessTime(testFilePath, newTime);
+                time = client.GetLastAccessTime(testFilePath);
+
+                DateTimeAssert.AreEqual(newTime.TruncateToWholeSeconds(), time);
+            }
+            finally
+            {
+                client.DeleteFile(testFilePath);
+            }
+        }
+
+
+        [TestMethod]
+        public void Sftp_SetLastAccessTimeUtc()
+        {
+            var testFilePath = "/home/sshnet/test-file.txt";
+            var testContent = "File";
+            using var client = new SftpClient(_connectionInfoFactory.Create());
+            client.Connect();
+
+            using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+            var currentTime = DateTime.UtcNow;
+
+            client.UploadFile(fileStream, testFilePath);
+            try
+            {
+                var time = client.GetLastAccessTimeUtc(testFilePath);
+
+                DateTimeAssert.AreEqual(currentTime.TruncateToWholeSeconds(), time);
+
+                var newTime = new DateTime(1986, 03, 15, 01, 02, 03, 123, DateTimeKind.Utc);
+
+                client.SetLastAccessTimeUtc(testFilePath, newTime);
+                time = client.GetLastAccessTimeUtc(testFilePath);
+
+                DateTimeAssert.AreEqual(newTime.TruncateToWholeSeconds(), time);
+            }
+            finally
+            {
+                client.DeleteFile(testFilePath); 
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SetLastWriteTime()
+        {
+            var testFilePath = "/home/sshnet/test-file.txt";
+            var testContent = "File";
+            using var client = new SftpClient(_connectionInfoFactory.Create());
+            client.Connect();
+
+            using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+            var currentTime = DateTime.Now;
+
+            client.UploadFile(fileStream, testFilePath);
+            try
+            {
+                var time = client.GetLastWriteTime(testFilePath);
+
+                DateTimeAssert.AreEqual(currentTime.TruncateToWholeSeconds(), time);
+
+                var newTime = new DateTime(1986, 03, 15, 01, 02, 03, 123, DateTimeKind.Local);
+
+                client.SetLastWriteTime(testFilePath, newTime);
+                time = client.GetLastWriteTime(testFilePath);
+
+                DateTimeAssert.AreEqual(newTime.TruncateToWholeSeconds(), time);
+            }
+            finally
+            {
+                client.DeleteFile(testFilePath);
+            }
+        }
+
+        [TestMethod]
+        public void Sftp_SetLastWriteTimeUtc()
+        {
+            var testFilePath = "/home/sshnet/test-file.txt";
+            var testContent = "File";
+            using var client = new SftpClient(_connectionInfoFactory.Create());
+            client.Connect();
+
+            using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
+            var currentTime = DateTime.UtcNow;
+            
+            client.UploadFile(fileStream, testFilePath);
+            try
+            {
+                var time = client.GetLastWriteTimeUtc(testFilePath);
+
+                DateTimeAssert.AreEqual(currentTime.TruncateToWholeSeconds(), time);
+
+                var newTime = new DateTime(1986, 03, 15, 01, 02, 03, 123, DateTimeKind.Utc);
+
+                client.SetLastWriteTimeUtc(testFilePath, newTime);
+                time = client.GetLastWriteTimeUtc(testFilePath);
+
+                DateTimeAssert.AreEqual(newTime.TruncateToWholeSeconds(), time);
+            }
+            finally
+            {
+                client.DeleteFile(testFilePath);
+            }
+        }
+
+        private static IEnumerable<object[]> GetSftpUploadFileFileStreamData()
+        {
+            yield return new object[] { 0 };
+            yield return new object[] { 5 * 1024 * 1024 };
+        }
+
+        private static Encoding GetRandomEncoding()
+        {
+            var random = new Random().Next(1, 3);
+            switch (random)
+            {
+                case 1:
+                    return Encoding.Unicode;
+                case 2:
+                    return Encoding.UTF8;
+                case 3:
+                    return Encoding.UTF32;
+                default:
+                    throw new NotImplementedException();
+            }
+        }
+
+        private static byte[] GetBytesWithPreamble(string text, Encoding encoding)
+        {
+            var preamble = encoding.GetPreamble();
+            var textBytes = encoding.GetBytes(text);
+
+            if (preamble.Length != 0)
+            {
+                var textAndPreambleBytes = new byte[preamble.Length + textBytes.Length];
+                Buffer.BlockCopy(preamble, srcOffset: 0, textAndPreambleBytes, dstOffset: 0, preamble.Length);
+                Buffer.BlockCopy(textBytes, srcOffset: 0, textAndPreambleBytes, preamble.Length, textBytes.Length);
+                return textAndPreambleBytes;
+            }
+
+            return textBytes;
+        }
+
+        private static Stream GetResourceStream(string resourceName)
+        {
+            var type = typeof(SftpTests);
+            var resourceStream = type.Assembly.GetManifestResourceStream(resourceName);
+            if (resourceStream == null)
+            {
+                throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName));
+            }
+            return resourceStream;
+        }
+
+        private static decimal CalculateTransferSpeed(long length, long elapsedMilliseconds)
+        {
+            return (length / 1024m) / (elapsedMilliseconds / 1000m);
+        }
+
+        private static void SftpCreateRemoteFile(SftpClient client, string remoteFile, int size)
+        {
+            var file = CreateTempFile(size);
+
+            try
+            {
+                using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
+                {
+                    client.UploadFile(fs, remoteFile);
+                }
+            }
+            finally
+            {
+                File.Delete(file);
+            }
+        }
+
+        private static byte[] GenerateRandom(int size)
+        {
+            var random = new Random();
+            var randomContent = new byte[size];
+            random.NextBytes(randomContent);
+            return randomContent;
+        }
+
+        private static Stream CreateStreamWithContent(string content)
+        {
+            var memoryStream = new MemoryStream();
+            var sw = new StreamWriter(memoryStream, Encoding.ASCII, 1024);
+            sw.Write(content);
+            sw.Flush();
+            memoryStream.Position = 0;
+            return memoryStream;
+        }
+
+        private static string GenerateUniqueRemoteFileName()
+        {
+            return $"/home/sshnet/{Guid.NewGuid():D}";
+        }
+    }
+}

+ 32 - 0
src/Renci.SshNet.IntegrationTests/SshClientTests.cs

@@ -0,0 +1,32 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    /// <summary>
+    /// The SSH client integration tests
+    /// </summary>
+    [TestClass]
+    public class SshClientTests : IntegrationTestBase, IDisposable
+    {
+        private readonly SshClient _sshClient;
+
+        public SshClientTests()
+        {
+            _sshClient = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
+            _sshClient.Connect();
+        }
+
+        [TestMethod]
+        public void Echo_Command_with_all_characters()
+        {
+            var builder = new StringBuilder();
+            var response = _sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'");
+
+            Assert.AreEqual("test !@#$%^&*()_+{}:,./<>[];\\|\n", response.Result);
+        }
+    
+        public void Dispose()
+        {
+            _sshClient.Disconnect();
+            _sshClient.Dispose();
+        }
+    }
+}

+ 41 - 0
src/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs

@@ -0,0 +1,41 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    internal class SshConnectionDisruptor
+    {
+        private readonly IConnectionInfoFactory _connectionInfoFactory;
+
+        public SshConnectionDisruptor(IConnectionInfoFactory connectionInfoFactory)
+        {
+            _connectionInfoFactory = connectionInfoFactory;
+        }
+
+        public SshConnectionRestorer BreakConnections()
+        {
+            var client = new SshClient(_connectionInfoFactory.Create());
+            
+            client.Connect();
+
+            PauseSshd(client);
+            
+            return new SshConnectionRestorer(client);
+        }
+
+        private static void PauseSshd(SshClient client)
+        {
+            var command = client.CreateCommand("sudo echo 'DenyUsers sshnet' >> /etc/ssh/sshd_config");
+            var output = command.Execute();
+            if (command.ExitStatus != 0)
+            {
+                throw new ApplicationException(
+                    $"Blocking user sshnet failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
+            }
+            command = client.CreateCommand("sudo pkill -9 -U sshnet -f sshd.pam");
+            output = command.Execute();
+            if (command.ExitStatus != 0)
+            {
+                throw new ApplicationException(
+                    $"Killing sshd.pam service failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
+            }
+        }
+    }
+}

+ 36 - 0
src/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs

@@ -0,0 +1,36 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    internal class SshConnectionRestorer : IDisposable
+    {
+        private SshClient _sshClient;
+
+        public SshConnectionRestorer(SshClient sshClient)
+        {
+            _sshClient = sshClient;
+        }
+
+        public void RestoreConnections()
+        {
+            var command = _sshClient.CreateCommand("sudo sed -i '/DenyUsers sshnet/d' /etc/ssh/sshd_config");
+            var output = command.Execute();
+            if (command.ExitStatus != 0)
+            {
+                throw new ApplicationException(
+                    $"Unblocking user sshnet failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
+            }
+            command = _sshClient.CreateCommand("sudo /usr/sbin/sshd.pam");
+            output = command.Execute();
+            if (command.ExitStatus != 0)
+            {
+                throw new ApplicationException(
+                    $"Resuming ssh service failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
+            }
+        }
+
+        public void Dispose()
+        {
+            _sshClient?.Dispose();
+            _sshClient = null;
+        }
+    }
+}

+ 972 - 0
src/Renci.SshNet.IntegrationTests/SshTests.cs

@@ -0,0 +1,972 @@
+using System.ComponentModel;
+using System.Net;
+using System.Net.Sockets;
+
+using Renci.SshNet.Common;
+using Renci.SshNet.IntegrationTests.Common;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class SshTests : TestBase
+    {
+        private IConnectionInfoFactory _connectionInfoFactory;
+        private IConnectionInfoFactory _adminConnectionInfoFactory;
+        private RemoteSshdConfig _remoteSshdConfig;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
+            _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
+
+            _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
+            _remoteSshdConfig.AllowTcpForwarding()
+                             .PrintMotd(false)
+                             .Update()
+                             .Restart();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            _remoteSshdConfig?.Reset();
+        }
+
+        /// <summary>
+        /// Test for a channel that is being closed by the server.
+        /// </summary>
+        [TestMethod]
+        public void Ssh_ShellStream_Exit()
+        {
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                var terminalModes = new Dictionary<TerminalModes, uint>
+                    {
+                        { TerminalModes.ECHO, 0 }
+                    };
+
+                using (var shellStream = client.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes))
+                {
+                    shellStream.WriteLine("echo Hello!");
+                    shellStream.WriteLine("exit");
+
+                    Thread.Sleep(1000);
+
+                    try
+                    {
+                        shellStream.Write("ABC");
+                        Assert.Fail();
+                    }
+                    catch (ObjectDisposedException ex)
+                    {
+                        Assert.IsNull(ex.InnerException);
+                        Assert.AreEqual("ShellStream", ex.ObjectName);
+                        Assert.AreEqual($"Cannot access a disposed object.{Environment.NewLine}Object name: '{ex.ObjectName}'.", ex.Message);
+                    }
+
+                    var line = shellStream.ReadLine();
+                    Assert.IsNotNull(line);
+                    Assert.IsTrue(line.EndsWith("Hello!"), line);
+
+                    // TODO: ReadLine should return null when the buffer is empty and the channel has been closed (issue #672)
+                    try
+                    {
+                        line = shellStream.ReadLine();
+                        Assert.Fail(line);
+                    }
+                    catch (NullReferenceException)
+                    {
+
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// https://github.com/sshnet/SSH.NET/issues/63
+        /// </summary>
+        [TestMethod]
+        [Category("Reproduction Tests")]
+        [Ignore]
+        public void Ssh_ShellStream_IntermittendOutput()
+        {
+            const string remoteFile = "/home/sshnet/test.sh";
+
+            var expectedResult = string.Join("\n",
+                                             "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 5 ",
+                                             "Line 6");
+
+            var scriptBuilder = new StringBuilder();
+            scriptBuilder.Append("#!/bin/sh\n");
+            scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("sleep .5\n");
+            scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("sleep 2\n");
+            scriptBuilder.Append("echo \"Line 5 \"\n");
+            scriptBuilder.Append("echo Line 6 \n");
+            scriptBuilder.Append("exit 13\n");
+
+            using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
+            {
+                sshClient.Connect();
+
+                CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
+
+                try
+                {
+                    var terminalModes = new Dictionary<TerminalModes, uint>
+                    {
+                        { TerminalModes.ECHO, 0 }
+                    };
+
+                    using (var shellStream = sshClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes))
+                    {
+                        shellStream.WriteLine(remoteFile);
+                        Thread.Sleep(1200);
+                        using (var reader = new StreamReader(shellStream, new UTF8Encoding(false), false, 10))
+                        {
+                            var lines = new List<string>();
+                            string line = null;
+                            while ((line = reader.ReadLine()) != null)
+                            {
+                                lines.Add(line);
+                            }
+                            Assert.AreEqual(6, lines.Count, string.Join("\n", lines));
+                            Assert.AreEqual(expectedResult, string.Join("\n", lines));
+                        }
+                    }
+                }
+                finally
+                {
+                    RemoveFileOrDirectory(sshClient, remoteFile);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Issue 1555
+        /// </summary>
+        [TestMethod]
+        public void Ssh_CreateShell()
+        {
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                using (var input = new MemoryStream())
+                using (var output = new MemoryStream())
+                using (var extOutput = new MemoryStream())
+                {
+                    var shell = client.CreateShell(input, output, extOutput);
+                    shell.Start();
+
+                    var inputWriter = new StreamWriter(input, Encoding.ASCII, 1024);
+                    inputWriter.WriteLine("echo $PATH");
+
+                    var outputReader = new StreamReader(output, Encoding.ASCII, false, 1024);
+                    Console.WriteLine(outputReader.ReadToEnd());
+
+                    shell.Stop();
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Ssh_Command_IntermittendOutput_EndExecute()
+        {
+            const string remoteFile = "/home/sshnet/test.sh";
+
+            var expectedResult = string.Join("\n",
+                                             "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 5 ",
+                                             "Line 6",
+                                             "");
+
+            var scriptBuilder = new StringBuilder();
+            scriptBuilder.Append("#!/bin/sh\n");
+            scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("sleep .5\n");
+            scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("sleep 2\n");
+            scriptBuilder.Append("echo \"Line 5 \"\n");
+            scriptBuilder.Append("echo Line 6 \n");
+            scriptBuilder.Append("exit 13\n");
+
+            using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
+            {
+                sshClient.Connect();
+
+                CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
+
+                try
+                {
+                    using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile))
+                    {
+                        cmd.Execute();
+
+                        Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+                    }
+
+                    using (var command = sshClient.CreateCommand(remoteFile))
+                    {
+                        var asyncResult = command.BeginExecute();
+                        var actualResult = command.EndExecute(asyncResult);
+
+                        Assert.AreEqual(expectedResult, actualResult);
+                        Assert.AreEqual(expectedResult, command.Result);
+                        Assert.AreEqual(13, command.ExitStatus);
+                    }
+                }
+                finally
+                {
+                    RemoveFileOrDirectory(sshClient, remoteFile);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Ignored for now, because:
+        /// * OutputStream.Read(...) does not block when no data is available
+        /// * SshCommand.(Begin)Execute consumes *OutputStream*, advancing its position.
+        /// 
+        /// https://github.com/sshnet/SSH.NET/issues/650
+        /// </summary>
+        [TestMethod]
+        [Ignore]
+        public void Ssh_Command_IntermittendOutput_OutputStream()
+        {
+            const string remoteFile = "/home/sshnet/test.sh";
+
+            var expectedResult = string.Join("\n",
+                                             "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+                                             "Line 5 ",
+                                             "Line 6");
+
+            var scriptBuilder = new StringBuilder();
+            scriptBuilder.Append("#!/bin/sh\n");
+            scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("sleep .5\n");
+            scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
+            scriptBuilder.Append("sleep 2\n");
+            scriptBuilder.Append("echo \"Line 5 \"\n");
+            scriptBuilder.Append("echo Line 6 \n");
+            scriptBuilder.Append("exit 13\n");
+
+            using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
+            {
+                sshClient.Connect();
+
+                CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
+
+                try
+                {
+                    using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile))
+                    {
+                        cmd.Execute();
+
+                        Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+                    }
+
+                    using (var command = sshClient.CreateCommand(remoteFile))
+                    {
+                        var asyncResult = command.BeginExecute();
+
+                        using (var reader = new StreamReader(command.OutputStream, new UTF8Encoding(false), false, 10))
+                        {
+                            var lines = new List<string>();
+                            string line = null;
+                            while ((line = reader.ReadLine()) != null)
+                            {
+                                lines.Add(line);
+                            }
+
+                            Assert.AreEqual(6, lines.Count, string.Join("\n", lines));
+                            Assert.AreEqual(expectedResult, string.Join("\n", lines));
+                            Assert.AreEqual(13, command.ExitStatus);
+                        }
+
+                        var actualResult = command.EndExecute(asyncResult);
+
+                        Assert.AreEqual(expectedResult, actualResult);
+                        Assert.AreEqual(expectedResult, command.Result);
+                    }
+                }
+                finally
+                {
+                    RemoveFileOrDirectory(sshClient, remoteFile);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Ssh_DynamicPortForwarding_DisposeSshClientWithoutStoppingPort()
+        {
+            const string searchText = "HTTP/1.1 301 Moved Permanently";
+            const string hostName = "github.com";
+
+            var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
+            Socket socksSocket;
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
+                client.Connect();
+
+                var forwardedPort = new ForwardedPortDynamic(1080);
+                forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
+                client.AddForwardedPort(forwardedPort);
+                forwardedPort.Start();
+
+                var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
+                                                    string.Empty,
+                                                    string.Empty);
+
+                socksSocket = socksClient.Connect(hostName, 80);
+                socksSocket.Send(httpGetRequest);
+
+                var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
+                Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
+            }
+
+            Assert.IsTrue(socksSocket.Connected);
+
+            // check if client socket was properly closed
+            Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
+        }
+
+        [TestMethod]
+        public void Ssh_DynamicPortForwarding_DomainName()
+        {
+            const string searchText = "HTTP/1.1 301 Moved Permanently";
+            const string hostName = "github.com";
+
+            // Set-up a host alias for google.be on the remote server that is not known locally; this allows us to
+            // verify whether the host name is resolved remotely.
+            const string hostNameAlias = "dynamicportforwarding-test.for.sshnet";
+
+            // Construct a HTTP request for which we expected the response to contain the search text.
+            var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
+
+            var ipAddresses = Dns.GetHostAddresses(hostName);
+            var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias);
+
+            try
+            {
+                using (var client = new SshClient(_connectionInfoFactory.Create()))
+                {
+                    client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
+                    client.Connect();
+
+                    var forwardedPort = new ForwardedPortDynamic(1080);
+                    forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
+                    client.AddForwardedPort(forwardedPort);
+                    forwardedPort.Start();
+
+                    var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
+                                                        string.Empty,
+                                                        string.Empty);
+                    var socksSocket = socksClient.Connect(hostNameAlias, 80);
+
+                    socksSocket.Send(httpGetRequest);
+                    var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
+                    Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
+
+                    // Verify if port is still open
+                    socksSocket.Send(httpGetRequest);
+                    GetHttpResponse(socksSocket, Encoding.ASCII);
+
+                    forwardedPort.Stop();
+
+                    Assert.IsTrue(socksSocket.Connected);
+
+                    // check if client socket was properly closed
+                    Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
+
+                    forwardedPort.Start();
+
+                    // create new SOCKS connection and very whether the forwarded port is functional again
+                    socksSocket = socksClient.Connect(hostNameAlias, 80);
+
+                    socksSocket.Send(httpGetRequest);
+                    httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
+                    Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
+
+                    forwardedPort.Dispose();
+
+                    Assert.IsTrue(socksSocket.Connected);
+
+                    // check if client socket was properly closed
+                    Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
+
+                    forwardedPort.Dispose();
+                }
+            }
+            finally
+            {
+                if (hostsFileUpdated)
+                {
+                    RemoveHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Ssh_DynamicPortForwarding_IPv4()
+        {
+            const string searchText = "HTTP/1.1 301 Moved Permanently";
+            const string hostName = "github.com";
+
+            var httpGetRequest = Encoding.ASCII.GetBytes($"GET /null HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
+
+            var ipv4 = Dns.GetHostAddresses(hostName).FirstOrDefault(p => p.AddressFamily == AddressFamily.InterNetwork);
+            Assert.IsNotNull(ipv4, $@"No IPv4 address found for '{hostName}'.");
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
+                client.Connect();
+
+                var forwardedPort = new ForwardedPortDynamic(1080);
+                forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
+                client.AddForwardedPort(forwardedPort);
+                forwardedPort.Start();
+
+                var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
+                                                    string.Empty,
+                                                    string.Empty);
+                var socksSocket = socksClient.Connect(new IPEndPoint(ipv4, 80));
+
+                socksSocket.Send(httpGetRequest);
+                var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
+                Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
+
+                forwardedPort.Dispose();
+
+                // check if client socket was properly closed
+                Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
+            }
+        }
+
+        /// <summary>
+        /// Verifies whether channels are effectively closed.
+        /// </summary>
+        [TestMethod]
+        public void Ssh_LocalPortForwardingCloseChannels()
+        {
+            const string hostNameAlias = "localportforwarding-test.for.sshnet";
+            const string hostName = "github.com";
+
+            var ipAddress = Dns.GetHostAddresses(hostName)[0];
+
+            var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
+
+            try
+            {
+                var connectionInfo = _connectionInfoFactory.Create();
+                connectionInfo.MaxSessions = 1;
+
+                using (var client = new SshClient(connectionInfo))
+                {
+                    client.Connect();
+
+                    var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225);
+
+                    for (var i = 0; i < (connectionInfo.MaxSessions + 1); i++)
+                    {
+                        var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(),
+                                                                   (uint)localEndPoint.Port,
+                                                                   hostNameAlias,
+                                                                   80);
+                        client.AddForwardedPort(forwardedPort);
+                        forwardedPort.Start();
+
+                        try
+                        {
+                            var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint);
+                            httpRequest.Host = hostName;
+                            httpRequest.Method = "GET";
+                            httpRequest.AllowAutoRedirect = false;
+
+                            try
+                            {
+                                using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
+                                {
+                                    Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
+                                }
+                            }
+                            catch (WebException ex)
+                            {
+                                Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status);
+                                Assert.IsNotNull(ex.Response);
+
+                                using (var httpResponse = ex.Response as HttpWebResponse)
+                                {
+                                    Assert.IsNotNull(httpResponse);
+                                    Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
+                                }
+                            }
+                        }
+                        finally
+                        {
+                            client.RemoveForwardedPort(forwardedPort);
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                if (hostsFileUpdated)
+                {
+                    RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Ssh_LocalPortForwarding()
+        {
+            const string hostNameAlias = "localportforwarding-test.for.sshnet";
+            const string hostName = "github.com";
+
+            var ipAddress = Dns.GetHostAddresses(hostName)[0];
+
+            var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
+
+            try
+            {
+                using (var client = new SshClient(_connectionInfoFactory.Create()))
+                {
+                    client.Connect();
+
+                    var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225);
+
+                    var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(),
+                                                               (uint)localEndPoint.Port,
+                                                               hostNameAlias,
+                                                               80);
+                    forwardedPort.Exception +=
+                        (sender, args) => Console.WriteLine(@"ForwardedPort exception: " + args.Exception);
+                    client.AddForwardedPort(forwardedPort);
+                    forwardedPort.Start();
+
+                    try
+                    {
+                        var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint);
+                        httpRequest.Host = hostName;
+                        httpRequest.Method = "GET";
+                        httpRequest.Accept = "text/html";
+                        httpRequest.AllowAutoRedirect = false;
+
+                        try
+                        {
+                            using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
+                            {
+                                Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
+                            }
+                        }
+                        catch (WebException ex)
+                        {
+                            Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status);
+                            Assert.IsNotNull(ex.Response);
+
+                            using (var httpResponse = ex.Response as HttpWebResponse)
+                            {
+                                Assert.IsNotNull(httpResponse);
+                                Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        client.RemoveForwardedPort(forwardedPort);
+                    }
+                }
+            }
+            finally
+            {
+                if (hostsFileUpdated)
+                {
+                    RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void Ssh_RemotePortForwarding()
+        {
+            var hostAddresses = Dns.GetHostAddresses(Dns.GetHostName());
+            var ipv4HostAddress = hostAddresses.First(p => p.AddressFamily == AddressFamily.InterNetwork);
+
+            var endpoint1 = new IPEndPoint(ipv4HostAddress, 666);
+            var endpoint2 = new IPEndPoint(ipv4HostAddress, 667);
+
+            var bytesReceivedOnListener1 = new List<byte>();
+            var bytesReceivedOnListener2 = new List<byte>();
+
+            using (var socketListener1 = new AsyncSocketListener(endpoint1))
+            using (var socketListener2 = new AsyncSocketListener(endpoint2))
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                socketListener1.BytesReceived += (received, socket) => bytesReceivedOnListener1.AddRange(received);
+                socketListener1.Start();
+
+                socketListener2.BytesReceived += (received, socket) => bytesReceivedOnListener2.AddRange(received);
+                socketListener2.Start();
+
+                client.Connect();
+
+                var forwardedPort1 = new ForwardedPortRemote(IPAddress.Loopback,
+                                                             10000,
+                                                             endpoint1.Address,
+                                                             (uint)endpoint1.Port);
+                forwardedPort1.Exception += (sender, args) => Console.WriteLine(@"forwardedPort1 exception: " + args.Exception);
+                client.AddForwardedPort(forwardedPort1);
+                forwardedPort1.Start();
+
+                var forwardedPort2 = new ForwardedPortRemote(IPAddress.Loopback,
+                                                             10001,
+                                                             endpoint2.Address,
+                                                             (uint)endpoint2.Port);
+                forwardedPort2.Exception += (sender, args) => Console.WriteLine(@"forwardedPort2 exception: " + args.Exception);
+                client.AddForwardedPort(forwardedPort2);
+                forwardedPort2.Start();
+
+                using (var s = client.CreateShellStream("a", 80, 25, 800, 600, 200))
+                {
+                    s.WriteLine($"telnet {forwardedPort1.BoundHost} {forwardedPort1.BoundPort}");
+                    s.Expect($"Connected to {forwardedPort1.BoundHost}\r\n");
+                    s.WriteLine("ABC");
+                    s.Flush();
+                    s.Expect("ABC");
+                    s.Close();
+                }
+
+                using (var s = client.CreateShellStream("b", 80, 25, 800, 600, 200))
+                {
+                    s.WriteLine($"telnet {forwardedPort2.BoundHost} {forwardedPort2.BoundPort}");
+                    s.Expect($"Connected to {forwardedPort2.BoundHost}\r\n");
+                    s.WriteLine("DEF");
+                    s.Flush();
+                    s.Expect("DEF");
+                    s.Close();
+                }
+
+                forwardedPort1.Stop();
+                forwardedPort2.Stop();
+            }
+
+            var textReceivedOnListener1 = Encoding.ASCII.GetString(bytesReceivedOnListener1.ToArray());
+            Assert.AreEqual("ABC\r\n", textReceivedOnListener1);
+
+            var textReceivedOnListener2 = Encoding.ASCII.GetString(bytesReceivedOnListener2.ToArray());
+            Assert.AreEqual("DEF\r\n", textReceivedOnListener2);
+        }
+
+        /// <summary>
+        /// Issue 1591
+        /// </summary>
+        [TestMethod]
+        public void Ssh_ExecuteShellScript()
+        {
+            const string remoteFile = "/home/sshnet/run.sh";
+            const string content = "#\bin\bash\necho Hello World!";
+
+            using (var client = new SftpClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                if (client.Exists(remoteFile))
+                {
+                    client.DeleteFile(remoteFile);
+                }
+
+                using (var memoryStream = new MemoryStream())
+                using (var sw = new StreamWriter(memoryStream, Encoding.ASCII))
+                {
+                    sw.Write(content);
+                    sw.Flush();
+                    memoryStream.Position = 0;
+                    client.UploadFile(memoryStream, remoteFile);
+                }
+            }
+
+            using (var client = new SshClient(_connectionInfoFactory.Create()))
+            {
+                client.Connect();
+
+                try
+                {
+                    var runChmod = client.RunCommand("chmod u+x " + remoteFile);
+                    runChmod.Execute();
+                    Assert.AreEqual(0, runChmod.ExitStatus, runChmod.Error);
+
+                    var runLs = client.RunCommand("ls " + remoteFile);
+                    var asyncResultLs = runLs.BeginExecute();
+
+                    var runScript = client.RunCommand(remoteFile);
+                    var asyncResultScript = runScript.BeginExecute();
+
+                    Assert.IsTrue(asyncResultScript.AsyncWaitHandle.WaitOne(10000));
+                    var resultScript = runScript.EndExecute(asyncResultScript);
+                    Assert.AreEqual("Hello World!\n", resultScript);
+
+                    Assert.IsTrue(asyncResultLs.AsyncWaitHandle.WaitOne(10000));
+                    var resultLs = runLs.EndExecute(asyncResultLs);
+                    Assert.AreEqual(remoteFile + "\n", resultLs);
+                }
+                finally
+                {
+                    RemoveFileOrDirectory(client, remoteFile);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Verifies if a hosts file contains an entry for a given combination of IP address and hostname,
+        /// and if necessary add either the host entry or an alias to an exist entry for the specified IP
+        /// address.
+        /// </summary>
+        /// <param name="linuxAdminConnectionFactory"></param>
+        /// <param name="ipAddress"></param>
+        /// <param name="hostName"></param>
+        /// <returns>
+        /// <see langword="true"/> if an entry was added or updated in the specified hosts file; otherwise,
+        /// <see langword="false"/>.
+        /// </returns>
+        private static bool AddOrUpdateHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory,
+                                                  IPAddress ipAddress,
+                                                  string hostName)
+        {
+            const string hostsFile = "/etc/hosts";
+
+            using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
+            {
+                client.Connect();
+
+                var hostConfig = HostConfig.Read(client, hostsFile);
+
+                var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress));
+                if (hostEntry != null)
+                {
+                    if (hostEntry.HostName == hostName)
+                    {
+                        return false;
+                    }
+
+                    foreach (var alias in hostEntry.Aliases)
+                    {
+                        if (alias == hostName)
+                        {
+                            return false;
+                        }
+                    }
+
+                    hostEntry.Aliases.Add(hostName);
+                }
+                else
+                {
+                    bool mappingFound = false;
+
+                    for (var i = (hostConfig.Entries.Count - 1); i >= 0; i--)
+                    {
+                        hostEntry = hostConfig.Entries[i];
+
+                        if (hostEntry.HostName == hostName)
+                        {
+                            if (hostEntry.IPAddress.Equals(ipAddress))
+                            {
+                                mappingFound = true;
+                                continue;
+                            }
+
+                            // If hostname is currently mapped to another IP address, then remove the 
+                            // current mapping
+                            hostConfig.Entries.RemoveAt(i);
+                        }
+                        else
+                        {
+                            for (var j = (hostEntry.Aliases.Count - 1); j >= 0; j--)
+                            {
+                                var alias = hostEntry.Aliases[j];
+
+                                if (alias == hostName)
+                                {
+                                    hostEntry.Aliases.RemoveAt(j);
+                                }
+                            }
+                        }
+                    }
+
+                    if (!mappingFound)
+                    {
+                        hostEntry = new HostEntry(ipAddress, hostName);
+                        hostConfig.Entries.Add(hostEntry);
+                    }
+                }
+
+                hostConfig.Write(client, hostsFile);
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// Remove the mapping between a given IP address and host name from the remote hosts file either by
+        /// removing a host entry entirely (if no other aliases are defined for the IP address) or removing
+        /// the aliases that match the host name for the IP address.
+        /// </summary>
+        /// <param name="linuxAdminConnectionFactory"></param>
+        /// <param name="ipAddress"></param>
+        /// <param name="hostName"></param>
+        /// <returns>
+        /// <see langword="true"/> if the hosts file was updated; otherwise, <see langword="false"/>.
+        /// </returns>
+        private static bool RemoveHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory,
+                                             IPAddress ipAddress,
+                                             string hostName)
+        {
+            const string hostsFile = "/etc/hosts";
+
+            using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
+            {
+                client.Connect();
+
+                var hostConfig = HostConfig.Read(client, hostsFile);
+
+                var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress));
+                if (hostEntry == null)
+                {
+                    return false;
+                }
+
+                if (hostEntry.HostName == hostName)
+                {
+                    if (hostEntry.Aliases.Count == 0)
+                    {
+                        hostConfig.Entries.Remove(hostEntry);
+                    }
+                    else
+                    {
+                        // Use one of the aliases (that are different from the specified host name) as host name
+                        // of the host entry.
+
+                        for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--)
+                        {
+                            var alias = hostEntry.Aliases[i];
+                            if (alias == hostName)
+                            {
+                                hostEntry.Aliases.RemoveAt(i);
+                            }
+                            else if (hostEntry.HostName == hostName)
+                            {
+                                // If we haven't already used one of the aliases as host name of the host entry
+                                // then do this now and remove the alias.
+
+                                hostEntry.HostName = alias;
+                                hostEntry.Aliases.RemoveAt(i);
+                            }
+                        }
+
+                        // If for some reason the host name of the host entry matched the specified host name
+                        // and it only had aliases that match the host name, then remove the host entry altogether.
+                        if (hostEntry.Aliases.Count == 0 && hostEntry.HostName == hostName)
+                        {
+                            hostConfig.Entries.Remove(hostEntry);
+                        }
+                    }
+                }
+                else
+                {
+                    var aliasRemoved = false;
+
+                    for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--)
+                    {
+                        if (hostEntry.Aliases[i] == hostName)
+                        {
+                            hostEntry.Aliases.RemoveAt(i);
+                            aliasRemoved = true;
+                        }
+                    }
+
+                    if (!aliasRemoved)
+                    {
+                        return false;
+                    }
+                }
+
+                hostConfig.Write(client, hostsFile);
+                return true;
+            }
+        }
+
+        private static string GetHttpResponse(Socket socket, Encoding encoding)
+        {
+            var httpResponseBuffer = new byte[2048];
+
+            // We expect:
+            // * The response to contain the searchText in the first receive.
+            // * The full response to be returned in the first receive.
+
+            var bytesReceived = socket.Receive(httpResponseBuffer,
+                                               0,
+                                               httpResponseBuffer.Length,
+                                               SocketFlags.None);
+            if (bytesReceived == 0)
+            {
+                return null;
+            }
+
+            if (bytesReceived == httpResponseBuffer.Length)
+            {
+                throw new Exception("We expect the HTTP response to be less than the buffer size. If not, we won't consume the full response.");
+            }
+
+            using (var sr = new StringReader(encoding.GetString(httpResponseBuffer, 0, bytesReceived)))
+            {
+                return sr.ReadToEnd();
+            }
+        }
+
+        private static void CreateShellScript(IConnectionInfoFactory connectionInfoFactory, string remoteFile, string script)
+        {
+            using (var sftpClient = new SftpClient(connectionInfoFactory.Create()))
+            {
+                sftpClient.Connect();
+                
+                using (var sw = sftpClient.CreateText(remoteFile, new UTF8Encoding(false)))
+                {
+                    sw.Write(script);
+                }
+
+                sftpClient.ChangePermissions(remoteFile, 0x1FF);
+            }
+        }
+
+        private static void RemoveFileOrDirectory(SshClient client, string remoteFile)
+        {
+            using (var cmd = client.CreateCommand("rm -Rf " + remoteFile))
+            {
+                cmd.Execute();
+                Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+            }
+        }
+    }
+}

+ 80 - 0
src/Renci.SshNet.IntegrationTests/TestBase.cs

@@ -0,0 +1,80 @@
+using System.Security.Cryptography;
+
+namespace Renci.SshNet.IntegrationTests
+{
+    public abstract class TestBase : IntegrationTestBase
+    {
+        protected static MemoryStream CreateMemoryStream(int size)
+        {
+            var memoryStream = new MemoryStream();
+            FillStream(memoryStream, size);
+            return memoryStream;
+        }
+
+        protected static void FillStream(Stream stream, int size)
+        {
+            var randomContent = new byte[50];
+            var random = new Random();
+
+            var numberOfBytesToWrite = size;
+
+            while (numberOfBytesToWrite > 0)
+            {
+                random.NextBytes(randomContent);
+
+                var numberOfCharsToWrite = Math.Min(numberOfBytesToWrite, randomContent.Length);
+                stream.Write(randomContent, 0, numberOfCharsToWrite);
+                numberOfBytesToWrite -= numberOfCharsToWrite;
+            }
+        }
+
+        protected static string CreateHash(Stream stream)
+        {
+            MD5 md5 = new MD5CryptoServiceProvider();
+            var hash = md5.ComputeHash(stream);
+            return Encoding.ASCII.GetString(hash);
+        }
+
+        protected static string CreateHash(byte[] buffer)
+        {
+            using (var ms = new MemoryStream(buffer))
+            {
+                return CreateHash(ms);
+            }
+        }
+
+        protected static string CreateFileHash(string path)
+        {
+            using (var fs = File.OpenRead(path))
+            {
+                return CreateHash(fs);
+            }
+        }
+
+        protected static string CreateTempFile(int size)
+        {
+            var file = Path.GetTempFileName();
+            CreateFile(file, size);
+            return file;
+        }
+
+        protected static void CreateFile(string fileName, int size)
+        {
+            using (var fs = File.OpenWrite(fileName))
+            {
+                FillStream(fs, size);
+            }
+        }
+
+        protected Stream GetManifestResourceStream(string resourceName)
+        {
+            var type = GetType();
+            var resourceStream = type.Assembly.GetManifestResourceStream(resourceName);
+            if (resourceStream == null)
+            {
+                throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName));
+            }
+            return resourceStream;
+        }
+    }
+}

+ 19 - 0
src/Renci.SshNet.IntegrationTests/TestInitializer.cs

@@ -0,0 +1,19 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    [TestClass]
+    public class TestInitializer
+    {
+        [AssemblyInitialize]
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "MSTests requires context parameter")]
+        public static async Task Initialize(TestContext context)
+        {
+            await InfrastructureFixture.Instance.InitializeAsync();
+        }
+
+        [AssemblyCleanup]
+        public static async Task Cleanup()
+        {
+            await InfrastructureFixture.Instance.DisposeAsync();
+        }
+    }
+}

+ 76 - 0
src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs

@@ -0,0 +1,76 @@
+using DotNet.Testcontainers.Builders;
+using DotNet.Testcontainers.Containers;
+using DotNet.Testcontainers.Images;
+
+namespace Renci.SshNet.IntegrationTests.TestsFixtures
+{
+    public sealed class InfrastructureFixture : IDisposable
+    {
+        private InfrastructureFixture()
+        {
+        }
+
+        private static readonly Lazy<InfrastructureFixture> InstanceLazy = new Lazy<InfrastructureFixture>(() => new InfrastructureFixture());
+
+        public static InfrastructureFixture Instance
+        {
+            get
+            {
+                return InstanceLazy.Value;
+            }
+        }
+
+        private IContainer _sshServer;
+
+        private IFutureDockerImage _sshServerImage;
+
+        public string SshServerHostName { get; set; }
+
+        public ushort SshServerPort { get; set; }
+
+        public SshUser AdminUser = new SshUser("sshnetadm", "ssh4ever");
+
+        public SshUser User = new SshUser("sshnet", "ssh4ever");
+
+        public async Task InitializeAsync()
+        {
+            _sshServerImage = new ImageFromDockerfileBuilder()
+                .WithName("renci-ssh-tests-server-image")
+                .WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), "Renci.SshNet.IntegrationTests")
+                .WithDockerfile("Dockerfile")
+                .WithDeleteIfExists(true)
+                
+                .Build();
+
+            await _sshServerImage.CreateAsync();
+
+            _sshServer = new ContainerBuilder()
+                .WithHostname("renci-ssh-tests-server")
+                .WithImage(_sshServerImage)
+                .WithPortBinding(22, true)
+                .Build();
+
+            await _sshServer.StartAsync();
+
+            SshServerPort = _sshServer.GetMappedPublicPort(22);
+            SshServerHostName = _sshServer.Hostname;
+        }
+
+        public async Task DisposeAsync()
+        {
+            if (_sshServer != null)
+            {
+                await _sshServer.DisposeAsync();
+            }
+
+            if (_sshServerImage != null)
+            {
+                await _sshServerImage.DisposeAsync();
+            }
+        }
+
+        public void Dispose()
+        {
+        }
+    }
+}

+ 85 - 0
src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs

@@ -0,0 +1,85 @@
+namespace Renci.SshNet.IntegrationTests.TestsFixtures
+{
+    /// <summary>
+    /// The base class for integration tests
+    /// </summary>
+    public abstract class IntegrationTestBase
+    {
+        private readonly InfrastructureFixture _infrastructureFixture;
+
+        /// <summary>
+        /// The SSH Server host name.
+        /// </summary>
+        public string SshServerHostName
+        {
+            get
+            {
+                return _infrastructureFixture.SshServerHostName;
+            }
+        }
+
+        /// <summary>
+        /// The SSH Server host name
+        /// </summary>
+        public ushort SshServerPort
+        {
+            get
+            {
+                return _infrastructureFixture.SshServerPort;
+            }
+        }
+
+        /// <summary>
+        /// The admin user that can use SSH Server.
+        /// </summary>
+        public SshUser AdminUser
+        {
+            get
+            {
+                return _infrastructureFixture.AdminUser;
+            }
+        }
+
+        /// <summary>
+        /// The normal user that can use SSH Server.
+        /// </summary>
+        public SshUser User
+        {
+            get
+            {
+                return _infrastructureFixture.User;
+            }
+        }
+
+        protected IntegrationTestBase()
+        {
+            _infrastructureFixture = InfrastructureFixture.Instance;
+            ShowInfrastructureInformation();
+        }
+
+        private void ShowInfrastructureInformation()
+        {
+            Console.WriteLine($"SSH Server host name: {_infrastructureFixture.SshServerHostName}");
+            Console.WriteLine($"SSH Server port: {_infrastructureFixture.SshServerPort}");
+        }
+
+        /// <summary>
+        /// Creates the test file.
+        /// </summary>
+        /// <param name="fileName">Name of the file.</param>
+        /// <param name="size">Size in megabytes.</param>
+        protected void CreateTestFile(string fileName, int size)
+        {
+            using (var testFile = File.Create(fileName))
+            {
+                var random = new Random();
+                for (int i = 0; i < 1024 * size; i++)
+                {
+                    var buffer = new byte[1024];
+                    random.NextBytes(buffer);
+                    testFile.Write(buffer, 0, buffer.Length);
+                }
+            }
+        }
+    }
+}

+ 16 - 0
src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs

@@ -0,0 +1,16 @@
+namespace Renci.SshNet.IntegrationTests.TestsFixtures
+{
+    public class SshUser
+    {
+        public string UserName { get; }
+
+        public string Password { get; }
+
+        public SshUser(string userName, string password)
+        {
+            UserName = userName;
+            Password = password;
+        }
+    }
+}
+

+ 8 - 0
src/Renci.SshNet.IntegrationTests/Users.cs

@@ -0,0 +1,8 @@
+namespace Renci.SshNet.IntegrationTests
+{
+    internal static class Users
+    {
+        public static readonly Credential Regular = new Credential("sshnet", "ssh4ever");
+        public static readonly Credential Admin = new Credential("sshnetadm", "ssh4ever");
+    }
+}

+ 5 - 0
src/Renci.SshNet.IntegrationTests/Usings.cs

@@ -0,0 +1,5 @@
+global using System.Text;
+
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+global using Renci.SshNet.IntegrationTests.TestsFixtures;

部分文件因为文件数量过多而无法显示