1use std::convert::TryFrom;
2
3use delegate::delegate;
4use ssh_encoding::Encode;
5
6#[cfg(feature = "flate2")]
7use crate::cipher::MAXIMUM_DECOMPRESSED_PACKET_LEN;
8
9#[derive(Debug, Clone)]
10pub enum Compression {
11 None,
12 #[cfg(feature = "flate2")]
13 Zlib,
14 #[cfg(feature = "flate2")]
15 ZlibOpenSSH,
16}
17
18#[derive(Debug)]
19pub enum Compress {
20 None,
21 #[cfg(feature = "flate2")]
22 Zlib(flate2::Compress),
23}
24
25#[derive(Debug)]
26pub enum Decompress {
27 None,
28 #[cfg(feature = "flate2")]
29 Zlib(flate2::Decompress),
30}
31
32#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
33pub struct Name(&'static str);
34impl AsRef<str> for Name {
35 fn as_ref(&self) -> &str {
36 self.0
37 }
38}
39
40impl Encode for Name {
41 delegate! { to self.as_ref() {
42 fn encoded_len(&self) -> Result<usize, ssh_encoding::Error>;
43 fn encode(&self, writer: &mut impl ssh_encoding::Writer) -> Result<(), ssh_encoding::Error>;
44 }}
45}
46
47impl TryFrom<&str> for Name {
48 type Error = ();
49 fn try_from(s: &str) -> Result<Name, ()> {
50 ALL_COMPRESSION_ALGORITHMS
51 .iter()
52 .find(|x| x.0 == s)
53 .map(|x| **x)
54 .ok_or(())
55 }
56}
57
58pub const NONE: Name = Name("none");
59#[cfg(feature = "flate2")]
60pub const ZLIB: Name = Name("zlib");
61#[cfg(feature = "flate2")]
62pub const ZLIB_LEGACY: Name = Name("zlib@openssh.com");
63
64pub const ALL_COMPRESSION_ALGORITHMS: &[&Name] = &[
65 &NONE,
66 #[cfg(feature = "flate2")]
67 &ZLIB,
68 #[cfg(feature = "flate2")]
69 &ZLIB_LEGACY,
70];
71
72#[cfg(feature = "flate2")]
73impl Compression {
74 pub fn new(name: &Name) -> Self {
75 if name == &ZLIB {
76 Compression::Zlib
77 } else if name == &ZLIB_LEGACY {
78 Compression::ZlibOpenSSH
79 } else {
80 Compression::None
81 }
82 }
83
84 pub fn init_compress(&self, comp: &mut Compress) {
85 match *self {
86 Compression::Zlib | Compression::ZlibOpenSSH => {
87 if let Compress::Zlib(ref mut c) = *comp {
88 c.reset()
89 } else {
90 *comp =
91 Compress::Zlib(flate2::Compress::new(flate2::Compression::fast(), true))
92 }
93 }
94 Compression::None => {
95 *comp = Compress::None;
96 }
97 }
98 }
99
100 pub fn init_decompress(&self, comp: &mut Decompress) {
101 match *self {
102 Compression::Zlib | Compression::ZlibOpenSSH => {
103 if let Decompress::Zlib(ref mut c) = *comp {
104 c.reset(true)
105 } else {
106 *comp = Decompress::Zlib(flate2::Decompress::new(true))
107 }
108 }
109 Compression::None => {
110 *comp = Decompress::None;
111 }
112 }
113 }
114}
115
116impl Compression {
117 pub fn is_deferred(&self) -> bool {
120 match self {
121 #[cfg(feature = "flate2")]
122 Compression::ZlibOpenSSH => true,
123 _ => false,
124 }
125 }
126}
127
128#[cfg(not(feature = "flate2"))]
129impl Compression {
130 pub fn new(_name: &Name) -> Self {
131 Compression::None
132 }
133
134 pub fn init_compress(&self, _: &mut Compress) {}
135
136 pub fn init_decompress(&self, _: &mut Decompress) {}
137}
138
139#[cfg(not(feature = "flate2"))]
140impl Compress {
141 pub fn compress<'a>(
142 &mut self,
143 input: &'a [u8],
144 _: &'a mut Vec<u8>,
145 ) -> Result<&'a [u8], crate::Error> {
146 Ok(input)
147 }
148
149 pub fn compress_into(
150 &mut self,
151 input: &[u8],
152 output: &mut Vec<u8>,
153 start_len: usize,
154 ) -> Result<usize, crate::Error> {
155 output.truncate(start_len);
156 output.extend_from_slice(input);
157 Ok(input.len())
158 }
159}
160
161#[cfg(not(feature = "flate2"))]
162impl Decompress {
163 pub fn decompress<'a>(
164 &mut self,
165 input: &'a [u8],
166 _: &'a mut Vec<u8>,
167 ) -> Result<&'a [u8], crate::Error> {
168 Ok(input)
169 }
170}
171
172#[cfg(all(test, feature = "flate2"))]
173mod tests {
174 use std::io::Write;
175
176 use flate2::write::ZlibEncoder;
177
178 use super::*;
179
180 #[test]
181 fn decompressed_packet_at_limit_is_accepted() {
182 let payload = vec![b'A'; MAXIMUM_DECOMPRESSED_PACKET_LEN];
183 let mut encoder = ZlibEncoder::new(Vec::new(), flate2::Compression::best());
184 encoder.write_all(&payload).unwrap();
185 let compressed = encoder.finish().unwrap();
186
187 let mut decompressor = Decompress::Zlib(flate2::Decompress::new(true));
188 let mut output = Vec::new();
189
190 let decompressed = decompressor.decompress(&compressed, &mut output).unwrap();
191 assert_eq!(decompressed.len(), MAXIMUM_DECOMPRESSED_PACKET_LEN);
192 }
193
194 #[test]
195 fn oversized_decompressed_packet_is_rejected() {
196 let payload = vec![b'A'; MAXIMUM_DECOMPRESSED_PACKET_LEN + 1024];
197 let mut encoder = ZlibEncoder::new(Vec::new(), flate2::Compression::best());
198 encoder.write_all(&payload).unwrap();
199 let compressed = encoder.finish().unwrap();
200
201 let mut decompressor = Decompress::Zlib(flate2::Decompress::new(true));
202 let mut output = Vec::new();
203
204 let err = decompressor.decompress(&compressed, &mut output).unwrap_err();
205 assert!(
206 matches!(err, crate::Error::PacketSize(len) if len > MAXIMUM_DECOMPRESSED_PACKET_LEN)
207 );
208 }
209
210 #[test]
211 fn empty_compressed_packet_does_not_spin() {
212 let compressed = Vec::new();
213 let mut decompressor = Decompress::Zlib(flate2::Decompress::new(true));
214 let mut output = Vec::new();
215
216 let decompressed = decompressor.decompress(&compressed, &mut output).unwrap();
217 assert!(decompressed.is_empty());
218 }
219}
220
221#[cfg(feature = "flate2")]
222impl Compress {
223 fn zlib_output_reserve_bound(input_len: usize) -> usize {
224 input_len.saturating_add(10)
225 }
226
227 pub fn compress<'a>(
228 &mut self,
229 input: &'a [u8],
230 output: &'a mut Vec<u8>,
231 ) -> Result<&'a [u8], crate::Error> {
232 match *self {
233 Compress::None => Ok(input),
234 Compress::Zlib(ref mut z) => {
235 output.clear();
236 let n_in = z.total_in() as usize;
237 let n_out = z.total_out() as usize;
238 output.resize(input.len() + 10, 0);
239 let flush = flate2::FlushCompress::Partial;
240 loop {
241 let n_in_ = z.total_in() as usize - n_in;
242 let n_out_ = z.total_out() as usize - n_out;
243 #[allow(clippy::indexing_slicing)] let c = z.compress(&input[n_in_..], &mut output[n_out_..], flush)?;
245 match c {
246 flate2::Status::BufError => {
247 output.resize(output.len() * 2, 0);
248 }
249 _ => break,
250 }
251 }
252 let n_out_ = z.total_out() as usize - n_out;
253 #[allow(clippy::indexing_slicing)] Ok(&output[..n_out_])
255 }
256 }
257 }
258
259 pub fn compress_into(
260 &mut self,
261 input: &[u8],
262 output: &mut Vec<u8>,
263 start_len: usize,
264 ) -> Result<usize, crate::Error> {
265 match *self {
266 Compress::None => {
267 output.truncate(start_len);
268 output.extend_from_slice(input);
269 Ok(input.len())
270 }
271 Compress::Zlib(ref mut z) => {
272 output.truncate(start_len);
273 let n_in = z.total_in() as usize;
274 let n_out = z.total_out() as usize;
275 let reserve = Self::zlib_output_reserve_bound(input.len());
276 output.resize(start_len + reserve, 0);
277 let flush = flate2::FlushCompress::Partial;
278 loop {
279 let n_in_ = z.total_in() as usize - n_in;
280 let n_out_ = z.total_out() as usize - n_out;
281 #[allow(clippy::indexing_slicing)] let c = z.compress(&input[n_in_..], &mut output[start_len + n_out_..], flush)?;
283 match c {
284 flate2::Status::BufError => {
285 let growth = output.len().saturating_sub(start_len).max(1);
286 output.resize(output.len() + growth, 0);
287 }
288 _ => break,
289 }
290 }
291 let n_out_ = z.total_out() as usize - n_out;
292 output.truncate(start_len + n_out_);
293 Ok(n_out_)
294 }
295 }
296 }
297}
298
299#[cfg(feature = "flate2")]
300impl Decompress {
301 pub fn decompress<'a>(
302 &mut self,
303 input: &'a [u8],
304 output: &'a mut Vec<u8>,
305 ) -> Result<&'a [u8], crate::Error> {
306 match *self {
307 Decompress::None => Ok(input),
308 Decompress::Zlib(ref mut z) => {
309 output.clear();
310 let n_in = z.total_in() as usize;
311 let n_out = z.total_out() as usize;
312 let max_output_len = MAXIMUM_DECOMPRESSED_PACKET_LEN
313 .checked_add(1)
314 .ok_or(crate::Error::PacketSize(usize::MAX))?;
315 output.resize(input.len().clamp(1, max_output_len), 0);
316 let flush = flate2::FlushDecompress::None;
317 loop {
318 let n_in_ = z.total_in() as usize - n_in;
319 let n_out_ = z.total_out() as usize - n_out;
320 #[allow(clippy::indexing_slicing)] let d = z.decompress(&input[n_in_..], &mut output[n_out_..], flush);
322 match d? {
323 flate2::Status::Ok | flate2::Status::BufError => {
324 let consumed_all_input = n_in_ == input.len();
325 let output_full = n_out_ == output.len();
326
327 if !output_full && consumed_all_input {
328 break;
329 }
330
331 if output_full {
332 if output.len() == max_output_len {
333 break;
334 }
335 let next_len = output
336 .len()
337 .checked_mul(2)
338 .map(|len| len.min(max_output_len))
339 .ok_or(crate::Error::PacketSize(usize::MAX))?;
340 output.resize(next_len, 0);
341 }
342 }
343 _ => break,
344 }
345 }
346 let n_out_ = z.total_out() as usize - n_out;
347 if n_out_ > MAXIMUM_DECOMPRESSED_PACKET_LEN {
348 return Err(crate::Error::PacketSize(n_out_));
349 }
350 #[allow(clippy::indexing_slicing)] Ok(&output[..n_out_])
352 }
353 }
354 }
355}