1use crate::stream::{BoxStream, Flow};
2use crate::{StreamError, StreamResult};
3use flate2::Compression as FlateCompression;
4use flate2::bufread::GzDecoder as GzBufDecoder;
5use flate2::write::{GzEncoder, ZlibEncoder};
6use flate2::{Decompress, FlushDecompress, Status};
7use std::collections::VecDeque;
8use std::io::{self, BufRead, Read, Write};
9
10const DECOMPRESS_CHUNK_SIZE: usize = 8192;
11const DECOMPRESS_OUTPUT_BUDGET: usize = DECOMPRESS_CHUNK_SIZE * 16;
14
15#[derive(Clone)]
16enum Terminal {
17 Complete,
18 Error(StreamError),
19}
20
21fn sticky_terminal<T>(terminal: &Terminal) -> Option<StreamResult<T>> {
22 match terminal {
23 Terminal::Complete => None,
24 Terminal::Error(error) => Some(Err(error.clone())),
25 }
26}
27
28fn codec_error<E: std::fmt::Display>(error: E) -> StreamError {
29 StreamError::Failed(error.to_string())
30}
31
32pub struct Compression;
38
39impl Compression {
40 #[must_use]
42 pub fn gzip() -> Flow<Vec<u8>, Vec<u8>> {
43 Flow::from_transform(|input| Box::new(CompressStream::gzip(input)) as BoxStream<Vec<u8>>)
44 }
45
46 #[must_use]
48 pub fn deflate() -> Flow<Vec<u8>, Vec<u8>> {
49 Flow::from_transform(|input| Box::new(CompressStream::deflate(input)) as BoxStream<Vec<u8>>)
50 }
51
52 #[must_use]
54 pub fn gunzip() -> Flow<Vec<u8>, Vec<u8>> {
55 Flow::from_transform(|input| Box::new(GunzipStream::new(input)) as BoxStream<Vec<u8>>)
56 }
57
58 #[must_use]
60 pub fn inflate() -> Flow<Vec<u8>, Vec<u8>> {
61 Flow::from_transform(|input| Box::new(InflateStream::new(input)) as BoxStream<Vec<u8>>)
62 }
63}
64
65enum EncoderKind {
66 Gzip(GzEncoder<Vec<u8>>),
67 Deflate(ZlibEncoder<Vec<u8>>),
68}
69
70impl EncoderKind {
71 fn write_all(&mut self, chunk: &[u8]) -> std::io::Result<()> {
72 match self {
73 Self::Gzip(codec) => codec.write_all(chunk),
74 Self::Deflate(codec) => codec.write_all(chunk),
75 }
76 }
77
78 fn try_finish(&mut self) -> std::io::Result<()> {
79 match self {
80 Self::Gzip(codec) => codec.try_finish(),
81 Self::Deflate(codec) => codec.try_finish(),
82 }
83 }
84
85 fn take_output(&mut self) -> Vec<u8> {
86 match self {
87 Self::Gzip(codec) => std::mem::take(codec.get_mut()),
88 Self::Deflate(codec) => std::mem::take(codec.get_mut()),
89 }
90 }
91}
92
93struct CompressStream {
94 input: BoxStream<Vec<u8>>,
95 codec: EncoderKind,
96 pending: VecDeque<Vec<u8>>,
97 finished: bool,
98 terminal: Option<Terminal>,
99}
100
101impl CompressStream {
102 fn gzip(input: BoxStream<Vec<u8>>) -> Self {
103 Self {
104 input,
105 codec: EncoderKind::Gzip(GzEncoder::new(Vec::new(), FlateCompression::default())),
106 pending: VecDeque::new(),
107 finished: false,
108 terminal: None,
109 }
110 }
111
112 fn deflate(input: BoxStream<Vec<u8>>) -> Self {
113 Self {
114 input,
115 codec: EncoderKind::Deflate(ZlibEncoder::new(Vec::new(), FlateCompression::default())),
116 pending: VecDeque::new(),
117 finished: false,
118 terminal: None,
119 }
120 }
121
122 fn fail<T>(&mut self, error: StreamError) -> Option<StreamResult<T>> {
123 self.terminal = Some(Terminal::Error(error.clone()));
124 Some(Err(error))
125 }
126
127 fn harvest_output(&mut self) {
128 let output = self.codec.take_output();
129 if !output.is_empty() {
130 self.pending.push_back(output);
131 }
132 }
133}
134
135impl Iterator for CompressStream {
136 type Item = StreamResult<Vec<u8>>;
137
138 fn next(&mut self) -> Option<Self::Item> {
139 if let Some(chunk) = self.pending.pop_front() {
140 return Some(Ok(chunk));
141 }
142 if let Some(terminal) = &self.terminal {
143 return sticky_terminal(terminal);
144 }
145
146 loop {
147 if self.finished {
148 self.terminal = Some(Terminal::Complete);
149 return None;
150 }
151
152 match self.input.next() {
153 Some(Ok(chunk)) => {
154 if let Err(error) = self.codec.write_all(&chunk).map_err(codec_error) {
155 return self.fail(error);
156 }
157 self.harvest_output();
158 if let Some(chunk) = self.pending.pop_front() {
159 return Some(Ok(chunk));
160 }
161 }
162 Some(Err(error)) => {
163 self.terminal = Some(Terminal::Error(error.clone()));
164 return Some(Err(error));
165 }
166 None => {
167 if let Err(error) = self.codec.try_finish().map_err(codec_error) {
168 return self.fail(error);
169 }
170 self.finished = true;
171 self.harvest_output();
172 if let Some(chunk) = self.pending.pop_front() {
173 return Some(Ok(chunk));
174 }
175 }
176 }
177 }
178 }
179}
180
181struct ChunkReader {
182 input: BoxStream<Vec<u8>>,
183 current: Vec<u8>,
184 offset: usize,
185 active: bool,
186 done: bool,
187 stream_error: Option<StreamError>,
188}
189
190impl ChunkReader {
191 fn new(input: BoxStream<Vec<u8>>) -> Self {
192 Self {
193 input,
194 current: Vec::new(),
195 offset: 0,
196 active: false,
197 done: false,
198 stream_error: None,
199 }
200 }
201
202 fn activate(&mut self) {
203 self.active = true;
204 }
205
206 fn stream_error(&self) -> Option<StreamError> {
207 self.stream_error.clone()
208 }
209}
210
211impl Read for ChunkReader {
212 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
213 let available = self.fill_buf()?;
214 let count = available.len().min(buf.len());
215 buf[..count].copy_from_slice(&available[..count]);
216 self.consume(count);
217 Ok(count)
218 }
219}
220
221impl BufRead for ChunkReader {
222 fn fill_buf(&mut self) -> io::Result<&[u8]> {
223 if !self.active {
224 return Err(io::ErrorKind::WouldBlock.into());
225 }
226
227 while self.offset >= self.current.len() && !self.done {
228 self.current.clear();
229 self.offset = 0;
230 match self.input.next() {
231 Some(Ok(chunk)) if chunk.is_empty() => {}
232 Some(Ok(chunk)) => self.current = chunk,
233 Some(Err(error)) => {
234 self.stream_error = Some(error.clone());
235 self.done = true;
236 return Err(io::Error::other(error.to_string()));
237 }
238 None => self.done = true,
239 }
240 }
241
242 Ok(&self.current[self.offset..])
243 }
244
245 fn consume(&mut self, amt: usize) {
246 self.offset = (self.offset + amt).min(self.current.len());
247 if self.offset == self.current.len() {
248 self.current.clear();
249 self.offset = 0;
250 }
251 }
252}
253
254struct GunzipStream {
255 codec: GzBufDecoder<ChunkReader>,
256 finished: bool,
257 terminal: Option<Terminal>,
258}
259
260impl GunzipStream {
261 fn new(input: BoxStream<Vec<u8>>) -> Self {
262 Self {
263 codec: GzBufDecoder::new(ChunkReader::new(input)),
264 finished: false,
265 terminal: None,
266 }
267 }
268
269 fn fail<T>(&mut self, error: StreamError) -> Option<StreamResult<T>> {
270 self.terminal = Some(Terminal::Error(error.clone()));
271 Some(Err(error))
272 }
273}
274
275impl Iterator for GunzipStream {
276 type Item = StreamResult<Vec<u8>>;
277
278 fn next(&mut self) -> Option<Self::Item> {
279 if let Some(terminal) = &self.terminal {
280 return sticky_terminal(terminal);
281 }
282 if self.finished {
283 self.terminal = Some(Terminal::Complete);
284 return None;
285 }
286
287 self.codec.get_mut().activate();
288 let mut output = vec![0_u8; DECOMPRESS_CHUNK_SIZE];
289 match self.codec.read(&mut output) {
290 Ok(0) => {
291 self.finished = true;
292 self.terminal = Some(Terminal::Complete);
293 None
294 }
295 Ok(count) => {
296 output.truncate(count);
297 output.shrink_to_fit();
298 Some(Ok(output))
299 }
300 Err(error) => {
301 let error = self
302 .codec
303 .get_ref()
304 .stream_error()
305 .unwrap_or_else(|| codec_error(error));
306 self.fail(error)
307 }
308 }
309 }
310}
311
312struct InflateStream {
313 input: BoxStream<Vec<u8>>,
314 codec: Decompress,
315 pending_input: Vec<u8>,
316 pending_output: VecDeque<Vec<u8>>,
317 pending_offset: usize,
318 draining: bool,
319 upstream_done: bool,
320 finished: bool,
321 terminal: Option<Terminal>,
322}
323
324impl InflateStream {
325 fn new(input: BoxStream<Vec<u8>>) -> Self {
326 Self {
327 input,
328 codec: Decompress::new(true),
329 pending_input: Vec::new(),
330 pending_output: VecDeque::new(),
331 pending_offset: 0,
332 draining: false,
333 upstream_done: false,
334 finished: false,
335 terminal: None,
336 }
337 }
338
339 fn fail<T>(&mut self, error: StreamError) -> Option<StreamResult<T>> {
340 self.terminal = Some(Terminal::Error(error.clone()));
341 Some(Err(error))
342 }
343
344 fn has_pending_input(&self) -> bool {
345 self.pending_offset < self.pending_input.len()
346 }
347
348 fn load_input(&mut self, chunk: Vec<u8>) {
349 self.pending_input = chunk;
350 self.pending_offset = 0;
351 }
352
353 fn clear_consumed_input(&mut self) {
354 if self.pending_offset >= self.pending_input.len() {
355 self.pending_input.clear();
356 self.pending_offset = 0;
357 }
358 }
359
360 fn pump_once(&mut self, flush: FlushDecompress) -> StreamResult<Pump> {
361 let before_in = self.codec.total_in();
362 let before_out = self.codec.total_out();
363 let mut output = vec![0_u8; DECOMPRESS_CHUNK_SIZE];
364 let status = self
365 .codec
366 .decompress(
367 &self.pending_input[self.pending_offset..],
368 &mut output,
369 flush,
370 )
371 .map_err(codec_error)?;
372 let consumed = (self.codec.total_in() - before_in) as usize;
373 let produced = (self.codec.total_out() - before_out) as usize;
374
375 self.pending_offset += consumed;
376 self.clear_consumed_input();
377
378 if matches!(status, Status::StreamEnd) {
379 self.finished = true;
380 }
381
382 output.truncate(produced);
383 if !output.is_empty() {
384 self.draining = produced == DECOMPRESS_CHUNK_SIZE && !self.finished;
385 output.shrink_to_fit();
386 return Ok(Pump::Output(output));
387 }
388
389 if matches!(status, Status::StreamEnd) {
390 return Ok(Pump::Complete);
391 }
392
393 if consumed == 0 {
394 self.draining = false;
395 return Ok(Pump::NeedInput);
396 }
397
398 Ok(Pump::Continue)
399 }
400}
401
402enum Pump {
403 Output(Vec<u8>),
404 NeedInput,
405 Continue,
406 Complete,
407}
408
409impl Iterator for InflateStream {
410 type Item = StreamResult<Vec<u8>>;
411
412 fn next(&mut self) -> Option<Self::Item> {
413 if let Some(chunk) = self.pending_output.pop_front() {
414 return Some(Ok(chunk));
415 }
416 if let Some(terminal) = &self.terminal {
417 return sticky_terminal(terminal);
418 }
419 if self.finished {
420 self.terminal = Some(Terminal::Complete);
421 return None;
422 }
423
424 let mut produced_this_pull = 0_usize;
425 let mut queued_output = false;
426 loop {
427 if produced_this_pull >= DECOMPRESS_OUTPUT_BUDGET {
428 break;
429 }
430 if !self.has_pending_input() && !self.upstream_done && !self.draining {
431 if queued_output {
432 break;
433 }
434 match self.input.next() {
435 Some(Ok(chunk)) => self.load_input(chunk),
436 Some(Err(error)) => {
437 self.terminal = Some(Terminal::Error(error.clone()));
438 return Some(Err(error));
439 }
440 None => self.upstream_done = true,
441 }
442 }
443
444 let flush = if self.upstream_done {
445 FlushDecompress::Finish
446 } else {
447 FlushDecompress::None
448 };
449
450 match self.pump_once(flush) {
451 Ok(Pump::Output(chunk)) => {
452 produced_this_pull += chunk.len();
453 queued_output = true;
454 self.pending_output.push_back(chunk);
455 }
456 Ok(Pump::Complete) => {
457 self.terminal = Some(Terminal::Complete);
458 break;
459 }
460 Ok(Pump::NeedInput) => {
461 if self.upstream_done {
462 return self.fail(StreamError::Failed(
463 "truncated compressed stream".to_owned(),
464 ));
465 }
466 if queued_output {
467 break;
468 }
469 }
470 Ok(Pump::Continue) => {}
471 Err(error) => return self.fail(error),
472 }
473 }
474
475 if let Some(chunk) = self.pending_output.pop_front() {
476 return Some(Ok(chunk));
477 }
478 if let Some(terminal) = &self.terminal {
479 return sticky_terminal(terminal);
480 }
481 None
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488 use crate::Source;
489
490 fn collect_chunks(flow: Flow<Vec<u8>, Vec<u8>>) -> Vec<Vec<u8>> {
491 Source::from_iter([b"hello ".to_vec(), b"world".to_vec()])
492 .via(flow)
493 .run_with(crate::Sink::collect())
494 .expect("flow materializes")
495 .wait()
496 .expect("flow completes")
497 }
498
499 #[test]
500 fn gzip_and_gunzip_round_trip() {
501 let compressed = collect_chunks(Compression::gzip());
502 let decoded = Source::from_iter(compressed)
503 .via(Compression::gunzip())
504 .run_with(crate::Sink::collect())
505 .expect("gunzip materializes")
506 .wait()
507 .expect("gunzip completes");
508
509 assert_eq!(decoded.concat(), b"hello world");
510 }
511
512 #[test]
513 fn deflate_and_inflate_round_trip() {
514 let compressed = collect_chunks(Compression::deflate());
515 let decoded = Source::from_iter(compressed)
516 .via(Compression::inflate())
517 .run_with(crate::Sink::collect())
518 .expect("inflate materializes")
519 .wait()
520 .expect("inflate completes");
521
522 assert_eq!(decoded.concat(), b"hello world");
523 }
524
525 #[test]
526 fn gunzip_completes_when_output_drains_before_finish() {
527 let compressed = collect_chunks(Compression::gzip()).concat();
532 let byte_chunks: Vec<Vec<u8>> = compressed.into_iter().map(|byte| vec![byte]).collect();
533
534 let decoded = Source::from_iter(byte_chunks)
535 .via(Compression::gunzip())
536 .run_with(crate::Sink::collect())
537 .expect("gunzip materializes")
538 .wait()
539 .expect("gunzip completes");
540
541 assert_eq!(decoded.concat(), b"hello world");
542 }
543
544 #[test]
545 fn inflate_completes_when_output_drains_before_finish() {
546 let compressed = collect_chunks(Compression::deflate()).concat();
547 let byte_chunks: Vec<Vec<u8>> = compressed.into_iter().map(|byte| vec![byte]).collect();
548
549 let decoded = Source::from_iter(byte_chunks)
550 .via(Compression::inflate())
551 .run_with(crate::Sink::collect())
552 .expect("inflate materializes")
553 .wait()
554 .expect("inflate completes");
555
556 assert_eq!(decoded.concat(), b"hello world");
557 }
558
559 #[test]
560 fn gunzip_fails_on_truncated_input() {
561 let compressed = collect_chunks(Compression::gzip());
562 let mut truncated = compressed.concat();
563 truncated.truncate(truncated.len().saturating_sub(2));
564
565 let result = Source::single(truncated)
566 .via(Compression::gunzip())
567 .run_with(crate::Sink::collect())
568 .expect("gunzip materializes")
569 .wait();
570
571 assert!(matches!(result, Err(StreamError::Failed(_))));
572 }
573
574 #[test]
575 fn inflate_fails_on_truncated_input() {
576 let compressed = collect_chunks(Compression::deflate());
577 let mut truncated = compressed.concat();
578 truncated.truncate(truncated.len() / 2);
579
580 let result = Source::single(truncated)
581 .via(Compression::inflate())
582 .run_with(crate::Sink::collect())
583 .expect("inflate materializes")
584 .wait();
585
586 assert!(matches!(result, Err(StreamError::Failed(_))));
587 }
588
589 #[test]
590 fn gunzip_emits_bounded_chunks_for_high_ratio_input() {
591 let payload = vec![b'x'; DECOMPRESS_CHUNK_SIZE * 4 + 17];
592 let compressed = Source::single(payload.clone())
593 .via(Compression::gzip())
594 .run_with(crate::Sink::collect())
595 .expect("gzip materializes")
596 .wait()
597 .expect("gzip completes");
598
599 let decoded = Source::single(compressed.concat())
600 .via(Compression::gunzip())
601 .run_with(crate::Sink::collect())
602 .expect("gunzip materializes")
603 .wait()
604 .expect("gunzip completes");
605
606 assert!(decoded.len() > 1);
607 assert!(
608 decoded
609 .iter()
610 .all(|chunk| chunk.len() <= DECOMPRESS_CHUNK_SIZE)
611 );
612 assert_eq!(decoded.concat(), payload);
613 }
614
615 #[test]
616 fn inflate_emits_bounded_chunks_for_high_ratio_input() {
617 let payload = vec![b'x'; DECOMPRESS_CHUNK_SIZE * 4 + 17];
618 let compressed = Source::single(payload.clone())
619 .via(Compression::deflate())
620 .run_with(crate::Sink::collect())
621 .expect("deflate materializes")
622 .wait()
623 .expect("deflate completes");
624
625 let decoded = Source::single(compressed.concat())
626 .via(Compression::inflate())
627 .run_with(crate::Sink::collect())
628 .expect("inflate materializes")
629 .wait()
630 .expect("inflate completes");
631
632 assert!(decoded.len() > 1);
633 assert!(
634 decoded
635 .iter()
636 .all(|chunk| chunk.len() <= DECOMPRESS_CHUNK_SIZE)
637 );
638 assert_eq!(decoded.concat(), payload);
639 }
640}