Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Zlib compression (.NET 6.0 onward only) #1326

Merged
merged 27 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b219168
Integrate `ZLibStream` from .NET 6.0+ with SSH.NET.
scott-xu Feb 17, 2024
beafd82
OpenSSH server does not support zlib (pre-auth); OpenSSH client still…
scott-xu Feb 17, 2024
37dc55c
Correct compression algorithm name; Update README.md
scott-xu Feb 17, 2024
8fb67f7
Integrate `ZLibStream` from .NET 6.0+ with SSH.NET.
scott-xu Feb 17, 2024
7762748
OpenSSH server does not support zlib (pre-auth); OpenSSH client still…
scott-xu Feb 17, 2024
e7cdd32
Correct compression algorithm name; Update README.md
scott-xu Feb 17, 2024
684298c
Merge branch 'develop' into zlib
scott-xu Feb 18, 2024
bcd275f
Merge branch 'develop' into zlib
scott-xu Feb 19, 2024
903e35c
Merge branch 'develop' into zlib
scott-xu Feb 21, 2024
6a6503e
Merge branch 'zlib' of https://github.com/scott-xu/SSH.NET into zlib
scott-xu Mar 16, 2024
38f7485
Merge branch 'sshnet:develop' into zlib
scott-xu Mar 16, 2024
a4c97b2
Merge branch 'zlib' of https://github.com/scott-xu/SSH.NET into zlib
scott-xu Mar 16, 2024
e7fcad3
Test the compression by upload/download file
scott-xu Mar 16, 2024
29f502c
Refactor compression.
scott-xu Mar 21, 2024
e2b6975
Move delayed compression logic to base class.
scott-xu Mar 21, 2024
3667296
seal Zlib
scott-xu Mar 21, 2024
cb98229
update unit test
scott-xu Mar 21, 2024
e7ff21e
update unit test to see if it can trigger integration test
scott-xu Mar 23, 2024
7ebf78c
Flush zlibStream
scott-xu Mar 23, 2024
16b0745
Fix integration test
scott-xu Mar 24, 2024
d5d74a1
update test
scott-xu Mar 24, 2024
8243215
Merge branch 'develop' into zlib
scott-xu Mar 24, 2024
8ef8583
Merge branch 'develop' of https://github.com/scott-xu/SSH.NET into zlib
scott-xu Apr 4, 2024
722f507
Merge branch 'develop' into zlib
scott-xu Apr 4, 2024
86ce319
Update ConnectionInfo.cs
scott-xu Apr 5, 2024
77c475d
Update README.md
scott-xu Apr 5, 2024
acbb217
Merge branch 'develop' into zlib
scott-xu Apr 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ Private keys can be encrypted using one of the following cipher methods:
* hmac-sha2-256-etm<span></span>@openssh.com
* hmac-sha2-512-etm<span></span>@openssh.com


## Compression

**SSH.NET** supports the following compression algorithms:
* none (default)
* zlib<span></span>@openssh.com (.NET 6 and higher)
scott-xu marked this conversation as resolved.
Show resolved Hide resolved
* zlib (.NET 6 and higher)

## Framework Support
**SSH.NET** supports the following target frameworks:
* .NETFramework 4.6.2 (and higher)
Expand Down
18 changes: 0 additions & 18 deletions src/Renci.SshNet/Compression/CompressionMode.cs

This file was deleted.

104 changes: 53 additions & 51 deletions src/Renci.SshNet/Compression/Compressor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.IO;

using Renci.SshNet.Messages.Authentication;
using Renci.SshNet.Security;

namespace Renci.SshNet.Compression
Expand All @@ -10,35 +10,23 @@ namespace Renci.SshNet.Compression
/// </summary>
public abstract class Compressor : Algorithm, IDisposable
{
private readonly ZlibStream _compressor;
private readonly ZlibStream _decompressor;
private MemoryStream _compressorStream;
private MemoryStream _decompressorStream;
private bool _isDisposed;

/// <summary>
/// Gets or sets a value indicating whether compression is active.
/// </summary>
/// <value>
/// <see langword="true"/> if compression is active; otherwise, <see langword="false"/>.
/// </value>
protected bool IsActive { get; set; }
private readonly bool _delayedCompression;

/// <summary>
/// Gets the session.
/// </summary>
protected Session Session { get; private set; }
private bool _isActive;
private Session _session;
private bool _isDisposed;

/// <summary>
/// Initializes a new instance of the <see cref="Compressor"/> class.
/// </summary>
protected Compressor()
/// <param name="delayedCompression">
/// <see langword="false"/> to start compression after receiving SSH_MSG_NEWKEYS.
/// <see langword="true"/> to delay compression util receiving SSH_MSG_USERAUTH_SUCCESS.
/// <see href="https://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt"/>.
/// </param>
protected Compressor(bool delayedCompression)
{
_compressorStream = new MemoryStream();
_decompressorStream = new MemoryStream();

_compressor = new ZlibStream(_compressorStream, CompressionMode.Compress);
_decompressor = new ZlibStream(_decompressorStream, CompressionMode.Decompress);
_delayedCompression = delayedCompression;
}

/// <summary>
Expand All @@ -47,7 +35,15 @@ protected Compressor()
/// <param name="session">The session.</param>
public virtual void Init(Session session)
{
Session = session;
if (_delayedCompression)
{
_session = session;
_session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
}
else
{
_isActive = true;
}
}

/// <summary>
Expand All @@ -57,7 +53,7 @@ public virtual void Init(Session session)
/// <returns>
/// The compressed data.
/// </returns>
public virtual byte[] Compress(byte[] data)
public byte[] Compress(byte[] data)
{
return Compress(data, 0, data.Length);
}
Expand All @@ -73,7 +69,7 @@ public virtual byte[] Compress(byte[] data)
/// </returns>
public virtual byte[] Compress(byte[] data, int offset, int length)
{
if (!IsActive)
if (!_isActive)
{
if (offset == 0 && length == data.Length)
{
Expand All @@ -85,21 +81,28 @@ public virtual byte[] Compress(byte[] data, int offset, int length)
return buffer;
}

_compressorStream.SetLength(0);

_compressor.Write(data, offset, length);

return _compressorStream.ToArray();
return CompressCore(data, offset, length);
}

/// <summary>
/// Compresses the specified data.
/// </summary>
/// <param name="data">Data to compress.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="data"/> at which to begin reading the data to compress. </param>
/// <param name="length">The number of bytes to be compressed. </param>
/// <returns>
/// The compressed data.
/// </returns>
protected abstract byte[] CompressCore(byte[] data, int offset, int length);

/// <summary>
/// Decompresses the specified data.
/// </summary>
/// <param name="data">Compressed data.</param>
/// <returns>
/// The decompressed data.
/// </returns>
public virtual byte[] Decompress(byte[] data)
public byte[] Decompress(byte[] data)
{
return Decompress(data, 0, data.Length);
}
Expand All @@ -115,7 +118,7 @@ public virtual byte[] Decompress(byte[] data)
/// </returns>
public virtual byte[] Decompress(byte[] data, int offset, int length)
{
if (!IsActive)
if (!_isActive)
{
if (offset == 0 && length == data.Length)
{
Expand All @@ -127,11 +130,24 @@ public virtual byte[] Decompress(byte[] data, int offset, int length)
return buffer;
}

_decompressorStream.SetLength(0);
return DecompressCore(data, offset, length);
}

_decompressor.Write(data, offset, length);
/// <summary>
/// Decompresses the specified data.
/// </summary>
/// <param name="data">Compressed data.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="data"/> at which to begin reading the data to decompress. </param>
/// <param name="length">The number of bytes to be read from the compressed data. </param>
/// <returns>
/// The decompressed data.
/// </returns>
protected abstract byte[] DecompressCore(byte[] data, int offset, int length);

return _decompressorStream.ToArray();
private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs<SuccessMessage> e)
{
_isActive = true;
_session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
}

/// <summary>
Expand All @@ -156,20 +172,6 @@ protected virtual void Dispose(bool disposing)

if (disposing)
{
var compressorStream = _compressorStream;
if (compressorStream != null)
{
compressorStream.Dispose();
_compressorStream = null;
}

var decompressorStream = _decompressorStream;
if (decompressorStream != null)
{
decompressorStream.Dispose();
_decompressorStream = null;
}

_isDisposed = true;
}
}
Expand Down
85 changes: 78 additions & 7 deletions src/Renci.SshNet/Compression/Zlib.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
namespace Renci.SshNet.Compression
#if NET6_0_OR_GREATER
using System.IO;
using System.IO.Compression;

namespace Renci.SshNet.Compression
{
/// <summary>
/// Represents "zlib" compression implementation.
/// </summary>
internal sealed class Zlib : Compressor
internal class Zlib : Compressor
{
private readonly ZLibStream _compressor;
private readonly ZLibStream _decompressor;
private MemoryStream _compressorStream;
private MemoryStream _decompressorStream;
private bool _isDisposed;

public Zlib()
: this(delayedCompression: false)
{
}

protected Zlib(bool delayedCompression)
: base(delayedCompression)
{
_compressorStream = new MemoryStream();
_decompressorStream = new MemoryStream();

_compressor = new ZLibStream(_compressorStream, CompressionMode.Compress);
_decompressor = new ZLibStream(_decompressorStream, CompressionMode.Decompress);
}

/// <summary>
/// Gets algorithm name.
/// </summary>
Expand All @@ -13,15 +38,61 @@ public override string Name
get { return "zlib"; }
}

protected override byte[] CompressCore(byte[] data, int offset, int length)
{
_compressorStream.SetLength(0);

_compressor.Write(data, offset, length);
_compressor.Flush();

return _compressorStream.ToArray();
}

protected override byte[] DecompressCore(byte[] data, int offset, int length)
{
_decompressorStream.Write(data, offset, length);
_decompressorStream.Position = 0;

using var outputStream = new MemoryStream();
_decompressor.CopyTo(outputStream);

_decompressorStream.SetLength(0);

return outputStream.ToArray();
}

/// <summary>
/// Initializes the algorithm.
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="session">The session.</param>
public override void Init(Session session)
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
base.Init(session);
base.Dispose(disposing);

if (_isDisposed)
{
return;
}

if (disposing)
{
var compressorStream = _compressorStream;
if (compressorStream != null)
{
compressorStream.Dispose();
_compressorStream = null;
}

var decompressorStream = _decompressorStream;
if (decompressorStream != null)
{
decompressorStream.Dispose();
_decompressorStream = null;
}

IsActive = true;
_isDisposed = true;
}
}
}
}
#endif
32 changes: 7 additions & 25 deletions src/Renci.SshNet/Compression/ZlibOpenSsh.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
using Renci.SshNet.Messages.Authentication;

#if NET6_0_OR_GREATER
namespace Renci.SshNet.Compression
{
/// <summary>
/// Represents "[email protected]" compression implementation.
/// </summary>
public class ZlibOpenSsh : Compressor
internal sealed class ZlibOpenSsh : Zlib
{
/// <summary>
/// Gets algorithm name.
/// </summary>
public override string Name
public ZlibOpenSsh()
: base(delayedCompression: true)
{
get { return "[email protected]"; }
}

/// <summary>
/// Initializes the algorithm.
/// </summary>
/// <param name="session">The session.</param>
public override void Init(Session session)
{
base.Init(session);

session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
}

private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs<SuccessMessage> e)
public override string Name
{
IsActive = true;
Session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
get { return "[email protected]"; }
}
}
}
#endif
Loading