From 0e11054cdbf94c903cb060d42edb8cfe2badc15e Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Tue, 11 Nov 2008 07:18:26 +0000 Subject: * Add compression option to hosts --- lib/src/main/java/com/trilead/ssh2/Connection.java | 21 ++++ .../ssh2/compression/CompressionFactory.java | 96 ++++++++++++++++ .../com/trilead/ssh2/compression/ICompressor.java | 30 +++++ .../java/com/trilead/ssh2/compression/Zlib.java | 121 +++++++++++++++++++++ .../com/trilead/ssh2/crypto/CryptoWishList.java | 3 + .../com/trilead/ssh2/packets/PacketKexInit.java | 5 +- .../com/trilead/ssh2/transport/KexManager.java | 11 +- .../ssh2/transport/TransportConnection.java | 53 ++++++++- .../trilead/ssh2/transport/TransportManager.java | 26 +++++ 9 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 lib/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java create mode 100644 lib/src/main/java/com/trilead/ssh2/compression/ICompressor.java create mode 100644 lib/src/main/java/com/trilead/ssh2/compression/Zlib.java diff --git a/lib/src/main/java/com/trilead/ssh2/Connection.java b/lib/src/main/java/com/trilead/ssh2/Connection.java index 2b244c2..1741a4c 100644 --- a/lib/src/main/java/com/trilead/ssh2/Connection.java +++ b/lib/src/main/java/com/trilead/ssh2/Connection.java @@ -89,6 +89,7 @@ public class Connection private AuthenticationManager am; private boolean authenticated = false; + private boolean compression = false; private ChannelManager cm; private CryptoWishList cryptoWishList = new CryptoWishList(); @@ -574,6 +575,20 @@ public class Connection tm.setConnectionMonitors(connectionMonitors); } + /** + * Controls whether compression is used on the link or not. + *

+ * Note: This can only be called before connect() + * @param enabled whether to enable compression + * @throws IOException + */ + public synchronized void setCompression(boolean enabled) throws IOException { + if (tm != null) + throw new IOException("Connection to " + hostname + " is already in connected state!"); + + compression = enabled; + } + /** * Close the connection to the SSH-2 server. All assigned sessions will be * closed, too. Can be called at any time. Don't forget to call this once @@ -735,6 +750,12 @@ public class Connection tm.setConnectionMonitors(connectionMonitors); + // Don't offer compression if not requested + if (!compression) { + cryptoWishList.c2s_comp_algos = new String[] { "none" }; + cryptoWishList.s2c_comp_algos = new String[] { "none" }; + } + /* * Make sure that the runnable below will observe the new value of "tm" * and "state" (the runnable will be executed in a different thread, diff --git a/lib/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java b/lib/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java new file mode 100644 index 0000000..30b3d21 --- /dev/null +++ b/lib/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java @@ -0,0 +1,96 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +package com.trilead.ssh2.compression; + +import java.util.Vector; + +/** + * @author Kenny Root + * + */ +public class CompressionFactory { + static class CompressorEntry + { + String type; + String compressorClass; + + public CompressorEntry(String type, String compressorClass) + { + this.type = type; + this.compressorClass = compressorClass; + } + } + + static Vector compressors = new Vector(); + + static + { + /* Higher Priority First */ + + compressors.addElement(new CompressorEntry("zlib", "com.trilead.ssh2.compression.Zlib")); + compressors.addElement(new CompressorEntry("zlib@openssh.com", "com.trilead.ssh2.compression.Zlib")); + compressors.addElement(new CompressorEntry("none", "")); + } + + public static String[] getDefaultCompressorList() + { + String list[] = new String[compressors.size()]; + for (int i = 0; i < compressors.size(); i++) + { + CompressorEntry ce = compressors.elementAt(i); + list[i] = new String(ce.type); + } + return list; + } + + public static void checkCompressorList(String[] compressorCandidates) + { + for (int i = 0; i < compressorCandidates.length; i++) + getEntry(compressorCandidates[i]); + } + + public static ICompressor createCompressor(String type) + { + try + { + CompressorEntry ce = getEntry(type); + if ("".equals(ce.compressorClass)) + return null; + + Class cc = Class.forName(ce.compressorClass); + ICompressor cmp = (ICompressor) cc.newInstance(); + + return cmp; + } + catch (Exception e) + { + throw new IllegalArgumentException("Cannot instantiate " + type); + } + } + + private static CompressorEntry getEntry(String type) + { + for (int i = 0; i < compressors.size(); i++) + { + CompressorEntry ce = compressors.elementAt(i); + if (ce.type.equals(type)) + return ce; + } + throw new IllegalArgumentException("Unkown algorithm " + type); + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/compression/ICompressor.java b/lib/src/main/java/com/trilead/ssh2/compression/ICompressor.java new file mode 100644 index 0000000..a71d31b --- /dev/null +++ b/lib/src/main/java/com/trilead/ssh2/compression/ICompressor.java @@ -0,0 +1,30 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package com.trilead.ssh2.compression; + +/** + * @author Kenny Root + * + */ +public interface ICompressor { + int getBufferSize(); + + int compress(byte[] buf, int start, int len, byte[] output); + + byte[] uncompress(byte[] buf, int start, int[] len); +} diff --git a/lib/src/main/java/com/trilead/ssh2/compression/Zlib.java b/lib/src/main/java/com/trilead/ssh2/compression/Zlib.java new file mode 100644 index 0000000..c81a719 --- /dev/null +++ b/lib/src/main/java/com/trilead/ssh2/compression/Zlib.java @@ -0,0 +1,121 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package com.trilead.ssh2.compression; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; + +/** + * @author Kenny Root + * + */ +public class Zlib implements ICompressor { + static private final int BUF_SIZE = 4096; + static private final int LEVEL = 5; + + private ZStream deflate; + private byte[] deflate_tmpbuf = new byte[BUF_SIZE]; + + private ZStream inflate; + private byte[] inflate_tmpbuf = new byte[BUF_SIZE]; + private byte[] inflated_buf; + + public Zlib() { + deflate = new ZStream(); + inflate = new ZStream(); + + deflate.deflateInit(LEVEL); + inflate.inflateInit(); + inflated_buf = new byte[BUF_SIZE]; + } + + public int getBufferSize() { + return BUF_SIZE; + } + + public int compress(byte[] buf, int start, int len, byte[] output) { + deflate.next_in = buf; + deflate.next_in_index = start; + deflate.avail_in = len - start; + + int status; + int outputlen = start; + + do { + deflate.next_out = deflate_tmpbuf; + deflate.next_out_index = 0; + deflate.avail_out = BUF_SIZE; + status = deflate.deflate(JZlib.Z_PARTIAL_FLUSH); + switch (status) { + case JZlib.Z_OK: + System.arraycopy(deflate_tmpbuf, 0, output, outputlen, BUF_SIZE + - deflate.avail_out); + outputlen += (BUF_SIZE - deflate.avail_out); + break; + default: + System.err.println("compress: deflate returnd " + status); + } + } while (deflate.avail_out == 0); + return outputlen; + } + + public byte[] uncompress(byte[] buffer, int start, int[] length) { + int inflated_end = 0; + + inflate.next_in = buffer; + inflate.next_in_index = start; + inflate.avail_in = length[0]; + + while (true) { + inflate.next_out = inflate_tmpbuf; + inflate.next_out_index = 0; + inflate.avail_out = BUF_SIZE; + int status = inflate.inflate(JZlib.Z_PARTIAL_FLUSH); + switch (status) { + case JZlib.Z_OK: + if (inflated_buf.length < inflated_end + BUF_SIZE + - inflate.avail_out) { + byte[] foo = new byte[inflated_end + BUF_SIZE + - inflate.avail_out]; + System.arraycopy(inflated_buf, 0, foo, 0, inflated_end); + inflated_buf = foo; + } + System.arraycopy(inflate_tmpbuf, 0, inflated_buf, inflated_end, + BUF_SIZE - inflate.avail_out); + inflated_end += (BUF_SIZE - inflate.avail_out); + length[0] = inflated_end; + break; + case JZlib.Z_BUF_ERROR: + if (inflated_end > buffer.length - start) { + byte[] foo = new byte[inflated_end + start]; + System.arraycopy(buffer, 0, foo, 0, start); + System.arraycopy(inflated_buf, 0, foo, start, inflated_end); + buffer = foo; + } else { + System.arraycopy(inflated_buf, 0, buffer, start, + inflated_end); + } + length[0] = inflated_end; + return buffer; + default: + System.err.println("uncompress: inflate returnd " + status); + return null; + } + } + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java b/lib/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java index c49befa..8fc64e5 100644 --- a/lib/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java +++ b/lib/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java @@ -1,6 +1,7 @@ package com.trilead.ssh2.crypto; +import com.trilead.ssh2.compression.CompressionFactory; import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; import com.trilead.ssh2.crypto.digest.MAC; import com.trilead.ssh2.transport.KexManager; @@ -20,4 +21,6 @@ public class CryptoWishList public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList(); public String[] c2s_mac_algos = MAC.getMacList(); public String[] s2c_mac_algos = MAC.getMacList(); + public String[] c2s_comp_algos = CompressionFactory.getDefaultCompressorList(); + public String[] s2c_comp_algos = CompressionFactory.getDefaultCompressorList(); } diff --git a/lib/src/main/java/com/trilead/ssh2/packets/PacketKexInit.java b/lib/src/main/java/com/trilead/ssh2/packets/PacketKexInit.java index 965ef06..7da5067 100644 --- a/lib/src/main/java/com/trilead/ssh2/packets/PacketKexInit.java +++ b/lib/src/main/java/com/trilead/ssh2/packets/PacketKexInit.java @@ -4,6 +4,7 @@ package com.trilead.ssh2.packets; import java.io.IOException; import java.security.SecureRandom; +import com.trilead.ssh2.compression.CompressionFactory; import com.trilead.ssh2.crypto.CryptoWishList; import com.trilead.ssh2.transport.KexParameters; @@ -31,8 +32,8 @@ public class PacketKexInit kp.encryption_algorithms_server_to_client = cwl.s2c_enc_algos; kp.mac_algorithms_client_to_server = cwl.c2s_mac_algos; kp.mac_algorithms_server_to_client = cwl.s2c_mac_algos; - kp.compression_algorithms_client_to_server = new String[] { "none" }; - kp.compression_algorithms_server_to_client = new String[] { "none" }; + kp.compression_algorithms_client_to_server = cwl.c2s_comp_algos; + kp.compression_algorithms_server_to_client = cwl.s2c_comp_algos; kp.languages_client_to_server = new String[] {}; kp.languages_server_to_client = new String[] {}; kp.first_kex_packet_follows = false; diff --git a/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java b/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java index 686e6cd..a2da737 100644 --- a/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java +++ b/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java @@ -7,6 +7,8 @@ import java.security.SecureRandom; import com.trilead.ssh2.ConnectionInfo; import com.trilead.ssh2.DHGexParameters; import com.trilead.ssh2.ServerHostKeyVerifier; +import com.trilead.ssh2.compression.CompressionFactory; +import com.trilead.ssh2.compression.ICompressor; import com.trilead.ssh2.crypto.CryptoWishList; import com.trilead.ssh2.crypto.KeyMaterial; import com.trilead.ssh2.crypto.cipher.BlockCipher; @@ -283,6 +285,7 @@ public class KexManager BlockCipher cbc; MAC mac; + ICompressor comp; try { @@ -290,6 +293,8 @@ public class KexManager km.initial_iv_client_to_server); mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server); + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_client_to_server); } catch (IllegalArgumentException e1) @@ -298,6 +303,7 @@ public class KexManager } tm.changeSendCipher(cbc, mac); + tm.changeSendCompression(comp); tm.kexFinished(); } @@ -464,6 +470,7 @@ public class KexManager BlockCipher cbc; MAC mac; + ICompressor comp; try { @@ -471,7 +478,8 @@ public class KexManager km.enc_key_server_to_client, km.initial_iv_server_to_client); mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client); - + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_server_to_client); } catch (IllegalArgumentException e1) { @@ -479,6 +487,7 @@ public class KexManager } tm.changeRecvCipher(cbc, mac); + tm.changeRecvCompression(comp); ConnectionInfo sci = new ConnectionInfo(); diff --git a/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java b/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java index a193503..2384773 100644 --- a/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java +++ b/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; +import com.trilead.ssh2.compression.ICompressor; import com.trilead.ssh2.crypto.cipher.BlockCipher; import com.trilead.ssh2.crypto.cipher.CipherInputStream; import com.trilead.ssh2.crypto.cipher.CipherOutputStream; @@ -50,6 +51,16 @@ public class TransportConnection byte[] recv_mac_buffer_cmp; int recv_padd_blocksize = 8; + + ICompressor recv_comp = null; + + ICompressor send_comp = null; + + boolean can_compress = false; + + byte[] recv_comp_buffer; + + byte[] send_comp_buffer; /* won't change */ @@ -101,7 +112,23 @@ public class TransportConnection if (send_padd_blocksize < 8) send_padd_blocksize = 8; } + + public void changeRecvCompression(ICompressor comp) + { + recv_comp = comp; + + if (comp != null) + recv_comp_buffer = new byte[comp.getBufferSize()]; + } + public void changeSendCompression(ICompressor comp) + { + send_comp = comp; + + if (comp != null) + send_comp_buffer = new byte[comp.getBufferSize()]; + } + public void sendMessage(byte[] message) throws IOException { sendMessage(message, 0, message.length, 0); @@ -124,6 +151,12 @@ public class TransportConnection padd = 4; else if (padd > 64) padd = 64; + + // TODO add compression somewhere here + if (send_comp != null && can_compress) { + len = send_comp.compress(message, off, len, send_comp_buffer); + message = send_comp_buffer; + } int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ @@ -279,6 +312,24 @@ public class TransportConnection + " bytes payload"); } - return payload_length; + if (recv_comp != null && can_compress) { + int[] uncomp_len = new int[] { payload_length }; + buffer = recv_comp.uncompress(buffer, off, uncomp_len); + + if (buffer == null) { + throw new IOException("Error while inflating remote data"); + } else { + return uncomp_len[0]; + } + } else { + return payload_length; + } + } + + /** + * + */ + public void startCompression() { + can_compress = true; } } diff --git a/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java b/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java index aeb4fce..c81dbdf 100644 --- a/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java +++ b/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java @@ -18,6 +18,7 @@ import com.trilead.ssh2.HTTPProxyData; import com.trilead.ssh2.HTTPProxyException; import com.trilead.ssh2.ProxyData; import com.trilead.ssh2.ServerHostKeyVerifier; +import com.trilead.ssh2.compression.ICompressor; import com.trilead.ssh2.crypto.Base64; import com.trilead.ssh2.crypto.CryptoWishList; import com.trilead.ssh2.crypto.cipher.BlockCipher; @@ -586,6 +587,27 @@ public class TransportManager tc.changeSendCipher(bc, mac); } + /** + * @param comp + */ + public void changeRecvCompression(ICompressor comp) { + tc.changeRecvCompression(comp); + } + + /** + * @param comp + */ + public void changeSendCompression(ICompressor comp) { + tc.changeSendCompression(comp); + } + + /** + * + */ + public void startCompression() { + tc.startCompression(); + } + public void sendAsynchronousMessage(byte[] msg) throws IOException { synchronized (asynchronousQueue) @@ -755,6 +777,10 @@ public class TransportManager continue; } + if (type == Packets.SSH_MSG_USERAUTH_SUCCESS) { + tc.startCompression(); + } + MessageHandler mh = null; for (int i = 0; i < messageHandlers.size(); i++) -- cgit v1.2.3