Skip to content

Commit b553f81

Browse files
scott-xuRob-Hague
andauthored
Add support for Zlib compression (.NET 6.0 onward only) (#1326)
* Integrate `ZLibStream` from .NET 6.0+ with SSH.NET. * OpenSSH server does not support zlib (pre-auth); OpenSSH client still supports zlib (pre-auth) * Correct compression algorithm name; Update README.md * Integrate `ZLibStream` from .NET 6.0+ with SSH.NET. * OpenSSH server does not support zlib (pre-auth); OpenSSH client still supports zlib (pre-auth) * Correct compression algorithm name; Update README.md * Test the compression by upload/download file * Refactor compression. * Move delayed compression logic to base class. * seal Zlib * update unit test * update unit test to see if it can trigger integration test * Flush zlibStream * Fix integration test * update test * Update ConnectionInfo.cs Co-authored-by: Rob Hague <[email protected]> * Update README.md --------- Co-authored-by: Rob Hague <[email protected]>
1 parent dd36d5b commit b553f81

File tree

10 files changed

+229
-161
lines changed

10 files changed

+229
-161
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
![Logo](https://raw.githubusercontent.com/sshnet/SSH.NET/develop/images/logo/png/SS-NET-icon-h50.png) SSH.NET
1+
![Logo](https://raw.githubusercontent.com/sshnet/SSH.NET/develop/images/logo/png/SS-NET-icon-h50.png) SSH.NET
22
=======
33
SSH.NET is a Secure Shell (SSH-2) library for .NET, optimized for parallelism.
44

@@ -123,6 +123,13 @@ Private keys can be encrypted using one of the following cipher methods:
123123
* hmac-sha2-256-etm<span></span>@openssh.com
124124
* hmac-sha2-512-etm<span></span>@openssh.com
125125

126+
127+
## Compression
128+
129+
**SSH.NET** supports the following compression algorithms:
130+
* none (default)
131+
* zlib<span></span>@openssh.com (.NET 6 and higher)
132+
126133
## Framework Support
127134

128135
**SSH.NET** supports the following target frameworks:

src/Renci.SshNet/Compression/CompressionMode.cs

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/Renci.SshNet/Compression/Compressor.cs

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2-
using System.IO;
32

3+
using Renci.SshNet.Messages.Authentication;
44
using Renci.SshNet.Security;
55

66
namespace Renci.SshNet.Compression
@@ -10,35 +10,23 @@ namespace Renci.SshNet.Compression
1010
/// </summary>
1111
public abstract class Compressor : Algorithm, IDisposable
1212
{
13-
private readonly ZlibStream _compressor;
14-
private readonly ZlibStream _decompressor;
15-
private MemoryStream _compressorStream;
16-
private MemoryStream _decompressorStream;
17-
private bool _isDisposed;
18-
19-
/// <summary>
20-
/// Gets or sets a value indicating whether compression is active.
21-
/// </summary>
22-
/// <value>
23-
/// <see langword="true"/> if compression is active; otherwise, <see langword="false"/>.
24-
/// </value>
25-
protected bool IsActive { get; set; }
13+
private readonly bool _delayedCompression;
2614

27-
/// <summary>
28-
/// Gets the session.
29-
/// </summary>
30-
protected Session Session { get; private set; }
15+
private bool _isActive;
16+
private Session _session;
17+
private bool _isDisposed;
3118

3219
/// <summary>
3320
/// Initializes a new instance of the <see cref="Compressor"/> class.
3421
/// </summary>
35-
protected Compressor()
22+
/// <param name="delayedCompression">
23+
/// <see langword="false"/> to start compression after receiving SSH_MSG_NEWKEYS.
24+
/// <see langword="true"/> to delay compression util receiving SSH_MSG_USERAUTH_SUCCESS.
25+
/// <see href="https://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt"/>.
26+
/// </param>
27+
protected Compressor(bool delayedCompression)
3628
{
37-
_compressorStream = new MemoryStream();
38-
_decompressorStream = new MemoryStream();
39-
40-
_compressor = new ZlibStream(_compressorStream, CompressionMode.Compress);
41-
_decompressor = new ZlibStream(_decompressorStream, CompressionMode.Decompress);
29+
_delayedCompression = delayedCompression;
4230
}
4331

4432
/// <summary>
@@ -47,7 +35,15 @@ protected Compressor()
4735
/// <param name="session">The session.</param>
4836
public virtual void Init(Session session)
4937
{
50-
Session = session;
38+
if (_delayedCompression)
39+
{
40+
_session = session;
41+
_session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
42+
}
43+
else
44+
{
45+
_isActive = true;
46+
}
5147
}
5248

5349
/// <summary>
@@ -57,7 +53,7 @@ public virtual void Init(Session session)
5753
/// <returns>
5854
/// The compressed data.
5955
/// </returns>
60-
public virtual byte[] Compress(byte[] data)
56+
public byte[] Compress(byte[] data)
6157
{
6258
return Compress(data, 0, data.Length);
6359
}
@@ -73,7 +69,7 @@ public virtual byte[] Compress(byte[] data)
7369
/// </returns>
7470
public virtual byte[] Compress(byte[] data, int offset, int length)
7571
{
76-
if (!IsActive)
72+
if (!_isActive)
7773
{
7874
if (offset == 0 && length == data.Length)
7975
{
@@ -85,21 +81,28 @@ public virtual byte[] Compress(byte[] data, int offset, int length)
8581
return buffer;
8682
}
8783

88-
_compressorStream.SetLength(0);
89-
90-
_compressor.Write(data, offset, length);
91-
92-
return _compressorStream.ToArray();
84+
return CompressCore(data, offset, length);
9385
}
9486

87+
/// <summary>
88+
/// Compresses the specified data.
89+
/// </summary>
90+
/// <param name="data">Data to compress.</param>
91+
/// <param name="offset">The zero-based byte offset in <paramref name="data"/> at which to begin reading the data to compress. </param>
92+
/// <param name="length">The number of bytes to be compressed. </param>
93+
/// <returns>
94+
/// The compressed data.
95+
/// </returns>
96+
protected abstract byte[] CompressCore(byte[] data, int offset, int length);
97+
9598
/// <summary>
9699
/// Decompresses the specified data.
97100
/// </summary>
98101
/// <param name="data">Compressed data.</param>
99102
/// <returns>
100103
/// The decompressed data.
101104
/// </returns>
102-
public virtual byte[] Decompress(byte[] data)
105+
public byte[] Decompress(byte[] data)
103106
{
104107
return Decompress(data, 0, data.Length);
105108
}
@@ -115,7 +118,7 @@ public virtual byte[] Decompress(byte[] data)
115118
/// </returns>
116119
public virtual byte[] Decompress(byte[] data, int offset, int length)
117120
{
118-
if (!IsActive)
121+
if (!_isActive)
119122
{
120123
if (offset == 0 && length == data.Length)
121124
{
@@ -127,11 +130,24 @@ public virtual byte[] Decompress(byte[] data, int offset, int length)
127130
return buffer;
128131
}
129132

130-
_decompressorStream.SetLength(0);
133+
return DecompressCore(data, offset, length);
134+
}
131135

132-
_decompressor.Write(data, offset, length);
136+
/// <summary>
137+
/// Decompresses the specified data.
138+
/// </summary>
139+
/// <param name="data">Compressed data.</param>
140+
/// <param name="offset">The zero-based byte offset in <paramref name="data"/> at which to begin reading the data to decompress. </param>
141+
/// <param name="length">The number of bytes to be read from the compressed data. </param>
142+
/// <returns>
143+
/// The decompressed data.
144+
/// </returns>
145+
protected abstract byte[] DecompressCore(byte[] data, int offset, int length);
133146

134-
return _decompressorStream.ToArray();
147+
private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs<SuccessMessage> e)
148+
{
149+
_isActive = true;
150+
_session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
135151
}
136152

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

157173
if (disposing)
158174
{
159-
var compressorStream = _compressorStream;
160-
if (compressorStream != null)
161-
{
162-
compressorStream.Dispose();
163-
_compressorStream = null;
164-
}
165-
166-
var decompressorStream = _decompressorStream;
167-
if (decompressorStream != null)
168-
{
169-
decompressorStream.Dispose();
170-
_decompressorStream = null;
171-
}
172-
173175
_isDisposed = true;
174176
}
175177
}

src/Renci.SshNet/Compression/Zlib.cs

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,35 @@
1-
namespace Renci.SshNet.Compression
1+
#if NET6_0_OR_GREATER
2+
using System.IO;
3+
using System.IO.Compression;
4+
5+
namespace Renci.SshNet.Compression
26
{
37
/// <summary>
48
/// Represents "zlib" compression implementation.
59
/// </summary>
6-
internal sealed class Zlib : Compressor
10+
internal class Zlib : Compressor
711
{
12+
private readonly ZLibStream _compressor;
13+
private readonly ZLibStream _decompressor;
14+
private MemoryStream _compressorStream;
15+
private MemoryStream _decompressorStream;
16+
private bool _isDisposed;
17+
18+
public Zlib()
19+
: this(delayedCompression: false)
20+
{
21+
}
22+
23+
protected Zlib(bool delayedCompression)
24+
: base(delayedCompression)
25+
{
26+
_compressorStream = new MemoryStream();
27+
_decompressorStream = new MemoryStream();
28+
29+
_compressor = new ZLibStream(_compressorStream, CompressionMode.Compress);
30+
_decompressor = new ZLibStream(_decompressorStream, CompressionMode.Decompress);
31+
}
32+
833
/// <summary>
934
/// Gets algorithm name.
1035
/// </summary>
@@ -13,15 +38,61 @@ public override string Name
1338
get { return "zlib"; }
1439
}
1540

41+
protected override byte[] CompressCore(byte[] data, int offset, int length)
42+
{
43+
_compressorStream.SetLength(0);
44+
45+
_compressor.Write(data, offset, length);
46+
_compressor.Flush();
47+
48+
return _compressorStream.ToArray();
49+
}
50+
51+
protected override byte[] DecompressCore(byte[] data, int offset, int length)
52+
{
53+
_decompressorStream.Write(data, offset, length);
54+
_decompressorStream.Position = 0;
55+
56+
using var outputStream = new MemoryStream();
57+
_decompressor.CopyTo(outputStream);
58+
59+
_decompressorStream.SetLength(0);
60+
61+
return outputStream.ToArray();
62+
}
63+
1664
/// <summary>
17-
/// Initializes the algorithm.
65+
/// Releases unmanaged and - optionally - managed resources.
1866
/// </summary>
19-
/// <param name="session">The session.</param>
20-
public override void Init(Session session)
67+
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
68+
protected override void Dispose(bool disposing)
2169
{
22-
base.Init(session);
70+
base.Dispose(disposing);
71+
72+
if (_isDisposed)
73+
{
74+
return;
75+
}
76+
77+
if (disposing)
78+
{
79+
var compressorStream = _compressorStream;
80+
if (compressorStream != null)
81+
{
82+
compressorStream.Dispose();
83+
_compressorStream = null;
84+
}
85+
86+
var decompressorStream = _decompressorStream;
87+
if (decompressorStream != null)
88+
{
89+
decompressorStream.Dispose();
90+
_decompressorStream = null;
91+
}
2392

24-
IsActive = true;
93+
_isDisposed = true;
94+
}
2595
}
2696
}
2797
}
98+
#endif
Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,17 @@
1-
using Renci.SshNet.Messages.Authentication;
2-
1+
#if NET6_0_OR_GREATER
32
namespace Renci.SshNet.Compression
43
{
5-
/// <summary>
6-
/// Represents "[email protected]" compression implementation.
7-
/// </summary>
8-
public class ZlibOpenSsh : Compressor
4+
internal sealed class ZlibOpenSsh : Zlib
95
{
10-
/// <summary>
11-
/// Gets algorithm name.
12-
/// </summary>
13-
public override string Name
6+
public ZlibOpenSsh()
7+
: base(delayedCompression: true)
148
{
15-
get { return "[email protected]"; }
169
}
1710

18-
/// <summary>
19-
/// Initializes the algorithm.
20-
/// </summary>
21-
/// <param name="session">The session.</param>
22-
public override void Init(Session session)
23-
{
24-
base.Init(session);
25-
26-
session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived;
27-
}
28-
29-
private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs<SuccessMessage> e)
11+
public override string Name
3012
{
31-
IsActive = true;
32-
Session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived;
13+
get { return "[email protected]"; }
3314
}
3415
}
3516
}
17+
#endif

0 commit comments

Comments
 (0)