Browse Source

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 years ago
parent
commit
2bbfee4a37
100 changed files with 16716 additions and 324 deletions
  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. BIN
      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/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)
 
+Copyright (c) Renci, Oleg Kapeljushnik, Gert Driesen and contributors
+
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 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
 * DSA in OpenSSL PEM and ssh.com 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:
 * DES-EDE3-CBC
@@ -97,6 +97,8 @@ Private keys can be encrypted using one of the following cipher methods:
 * ecdsa-sha2-nistp256
 * ecdsa-sha2-nistp384
 * ecdsa-sha2-nistp521
+* rsa-sha2-512
+* rsa-sha2-256
 * ssh-rsa
 * ssh-dss
 
@@ -116,15 +118,9 @@ Private keys can be encrypted using one of the following cipher methods:
 
 ## Framework Support
 **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
 
@@ -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:
 
 ```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"))
 {
     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();
 }
 ```
 
-## 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
 
 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:
-  - nuget restore src\Renci.SshNet.VS2019.sln
+  - nuget restore src\Renci.SshNet.sln
 
 build:
-  project: src\Renci.SshNet.VS2019.sln
+  project: src\Renci.SshNet.sln
   verbosity: minimal
 
 test_script:
 - cmd: >-
-    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net40\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"
-
-    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net35\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"
+    vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\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>
 		<MSBuildTasksPackageVersion>1.5.0.214</MSBuildTasksPackageVersion>
 	</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>
-		<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>
-		<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 Include=".NETStandard 2.0">
 			<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard2.0</OutputDirectory>
 			<Moniker>netstandard2.0</Moniker>
 		</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>
-	
-	<Target Name="CleanClassic" DependsOnTargets="CleanSolutionClassic">
-		<RemoveDir Directories="$(MSBuildThisFileDirectory)target"/>
-	</Target>
-	
+
 	<Target Name="CleanModern" DependsOnTargets="CleanSolutionModern">
 		<RemoveDir Directories="$(MSBuildThisFileDirectory)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)">
 		<ItemGroup>
@@ -99,26 +54,11 @@
 		<MSBuild Projects="@(ProjectToBuild)" Targets="Clean"/>
 	</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)">
 		<Message Text="Restoring nuget packages for '%(VisualStudioVersionModern.SolutionFile)'..." Importance="High"/>
 		<Exec Command="$(NuGetExe) restore &quot;%(VisualStudioVersionModern.SolutionFile)&quot;"/>
 	</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)">
 		<ItemGroup>
 			<ProjectToBuild Remove="@(ProjectToBuild)"/>
@@ -131,12 +71,7 @@
 	
 	<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)">
 		<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"/>
 	</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)">
 		<ItemGroup>
 			<BuildOutput Remove="@(BuildOutput)"/>

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

@@ -6,7 +6,7 @@
         <title>SSH.NET</title>
         <authors>Renci</authors>
         <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>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
         <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>
         <tags>ssh scp sftp</tags>
         <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">
             <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]" />
           </group>
-          <group targetFramework="wp8">
+          <group targetFramework="net6.0">
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
           </group>
-          <group targetFramework="uap10.0">
+          <group targetFramework="net7.0">
             <dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
-            <dependency id="System.Xml.XPath.XmlDocument" version="4.3.0" />
           </group>
         </dependencies>
     </metadata>

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

@@ -1,21 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
   <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>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+
     <SchemaVersion>2.0</SchemaVersion>
     <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>Documentation</AssemblyName>
     <RootNamespace>Documentation</RootNamespace>
     <Name>Documentation</Name>
     <!-- SHFB properties -->
-    <FrameworkVersion>.NET Framework 4.0</FrameworkVersion>
+    <FrameworkVersion>.NET Framework 4.6.2</FrameworkVersion>
     <OutputPath>..\target\help</OutputPath>
     <HtmlHelpName>SshNet.Help</HtmlHelpName>
     <Language>en-US</Language>
@@ -24,25 +25,15 @@
     <SyntaxFilters>C#</SyntaxFilters>
     <SdkLinkTarget>Blank</SdkLinkTarget>
     <RootNamespaceContainer>False</RootNamespaceContainer>
-    <PresentationStyle>VS2010</PresentationStyle>
+    <PresentationStyle>VS2013</PresentationStyle>
     <Preliminary>False</Preliminary>
     <NamingMethod>Guid</NamingMethod>
-    <HelpTitle>SSH.NET Client Library Documenation</HelpTitle>
+    <HelpTitle>SSH.NET Client Library Documentation</HelpTitle>
     <ContentPlacement>AboveNamespaces</ContentPlacement>
-    <ComponentConfigurations>
-      <ComponentConfig id="Code Block Component" enabled="True">
-        <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>
-      <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>
     <MissingTags>Summary, Parameter, Returns, AutoDocumentCtors, TypeParameter, AutoDocumentDispose</MissingTags>
     <BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
@@ -54,7 +45,7 @@
     <CleanIntermediates>True</CleanIntermediates>
   </PropertyGroup>
   <!-- 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>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@@ -73,4 +64,4 @@
   </PropertyGroup>
   <!-- Import the SHFB build targets -->
   <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
-</Project>
+</Project>

BIN
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>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         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.ChangeDirectory("/asdasd");
@@ -24,11 +21,10 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         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.ChangeDirectory("/asdasd/");
@@ -37,11 +33,10 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         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.ChangeDirectory("/asdasd/sssddds");
@@ -50,11 +45,10 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         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.ChangeDirectory("/asdasd/sssddds/");
@@ -63,10 +57,9 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         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.ChangeDirectory("/usr");
@@ -76,10 +69,9 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         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.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>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPermissionDeniedException))]
         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();
 
@@ -37,11 +29,10 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPathNotFoundException))]
         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();
 
@@ -57,12 +48,11 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginDownloadFile")]
         [ExpectedException(typeof(ArgumentNullException))]
         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.BeginDownloadFile("aaaa", null, null, null);
@@ -72,12 +62,11 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginDownloadFile")]
         [ExpectedException(typeof(ArgumentException))]
         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.BeginDownloadFile("   ", new MemoryStream(), null, null);
@@ -87,12 +76,11 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginDownloadFile")]
         [ExpectedException(typeof(ArgumentException))]
         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.BeginDownloadFile(null, new MemoryStream(), null, null);
@@ -102,15 +90,14 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(ArgumentException))]
         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();
                 var filename = Path.GetTempFileName();
-                this.CreateTestFile(filename, 1);
+                CreateTestFile(filename, 1);
                 sftp.UploadFile(File.OpenRead(filename), "test123");
                 var async1 = sftp.BeginListDirectory("/", 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>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         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();
 
@@ -23,7 +17,7 @@ namespace Renci.SshNet.Tests.Classes
                 string remoteFileName1 = Path.GetRandomFileName();
                 string remoteFileName2 = Path.GetRandomFileName();
 
-                this.CreateTestFile(uploadedFileName, 1);
+                CreateTestFile(uploadedFileName, 1);
 
                 using (var file = File.OpenRead(uploadedFileName))
                 {
@@ -42,12 +36,11 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to RenameFile.")]
         [ExpectedException(typeof(ArgumentNullException))]
         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();
 
@@ -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>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_SynchronizeDirectories()
         {
             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();
 
                 string uploadedFileName = Path.GetTempFileName();
 
                 string sourceDir = Path.GetDirectoryName(uploadedFileName);
-                string destDir = "/";
+                string destDir = "/home/sshnet/";
                 string searchPattern = Path.GetFileName(uploadedFileName);
                 var upLoadedFiles = sftp.SynchronizeDirectories(sourceDir, destDir, searchPattern);
 
@@ -42,19 +37,18 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_BeginSynchronizeDirectories()
         {
             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();
 
                 string uploadedFileName = Path.GetTempFileName();
 
                 string sourceDir = Path.GetDirectoryName(uploadedFileName);
-                string destDir = "/";
+                string destDir = "/home/sshnet/";
                 string searchPattern = Path.GetFileName(uploadedFileName);
 
                 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.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>
     /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
     /// </summary>
-    public partial class SftpClientTest
+    public partial class SftpClientTest : IntegrationTestBase
     {
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_Upload_And_Download_1MB_File()
         {
             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();
 
-                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
                 var uploadedHash = CalculateMD5(uploadedFileName);
@@ -38,7 +31,7 @@ namespace Renci.SshNet.Tests.Classes
                     sftp.UploadFile(file, remoteFileName);
                 }
 
-                string downloadedFileName = Path.GetTempFileName();
+                var downloadedFileName = Path.GetTempFileName();
 
                 using (var file = File.OpenWrite(downloadedFileName))
                 {
@@ -60,18 +53,17 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(SftpPermissionDeniedException))]
         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();
 
-                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))
                 {
@@ -84,7 +76,6 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each()
         {
             var maxFiles = 10;
@@ -92,20 +83,23 @@ namespace Renci.SshNet.Tests.Classes
 
             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();
 
                 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
                     testInfo.UploadedHash = CalculateMD5(testInfo.UploadedFileName);
@@ -130,7 +124,7 @@ namespace Renci.SshNet.Tests.Classes
                 }
 
                 //  Wait for upload to finish
-                bool uploadCompleted = false;
+                var uploadCompleted = false;
                 while (!uploadCompleted)
                 {
                     //  Assume upload completed
@@ -174,7 +168,7 @@ namespace Renci.SshNet.Tests.Classes
                 }
 
                 //  Wait for download to finish
-                bool downloadCompleted = false;
+                var downloadCompleted = false;
                 while (!downloadCompleted)
                 {
                     //  Assume download completed
@@ -206,6 +200,11 @@ namespace Renci.SshNet.Tests.Classes
 
                     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))
                     {
                         uploadDownloadSizeOk = false;
@@ -238,21 +237,20 @@ namespace Renci.SshNet.Tests.Classes
         //  TODO:   Split this test into multiple tests
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [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()
         {
             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();
 
-                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;
 
                 // Test for BeginUploadFile.
@@ -261,11 +259,14 @@ namespace Renci.SshNet.Tests.Classes
 
                 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)
                     {
@@ -282,11 +283,14 @@ namespace Renci.SshNet.Tests.Classes
                 asyncResult = null;
                 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)
                     {
@@ -301,11 +305,13 @@ namespace Renci.SshNet.Tests.Classes
                 // Test for BeginListDirectory.
 
                 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)
                 {
@@ -318,64 +324,60 @@ namespace Renci.SshNet.Tests.Classes
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginUploadFile")]
         [ExpectedException(typeof(ArgumentNullException))]
         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.BeginUploadFile(null, "aaaaa", null, null);
+                _ = sftp.BeginUploadFile(null, "aaaaa", null, null);
                 sftp.Disconnect();
             }
         }
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginUploadFile")]
         [ExpectedException(typeof(ArgumentException))]
         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.BeginUploadFile(new MemoryStream(), "   ", null, null);
+                _ = sftp.BeginUploadFile(new MemoryStream(), "   ", null, null);
                 sftp.Disconnect();
             }
         }
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [Description("Test passing null to BeginUploadFile")]
         [ExpectedException(typeof(ArgumentException))]
         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.BeginUploadFile(new MemoryStream(), null, null, null);
+                _ = sftp.BeginUploadFile(new MemoryStream(), null, null, null);
                 sftp.Disconnect();
             }
         }
 
         [TestMethod]
         [TestCategory("Sftp")]
-        [TestCategory("integration")]
         [ExpectedException(typeof(ArgumentException))]
         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();
                 var async1 = sftp.BeginListDirectory("/", null, null);
                 var filename = Path.GetTempFileName();
-                this.CreateTestFile(filename, 100);
+                CreateTestFile(filename, 100);
                 var async2 = sftp.BeginUploadFile(File.OpenRead(filename), "test", null, null);
                 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;

Some files were not shown because too many files changed in this diff