| 1 | //! Extra streaming compression functionality. |
| 2 | //! |
| 3 | //! As of now this is mainly intended for use to build a higher-level wrapper. |
| 4 | //! |
| 5 | //! There is no DeflateState as the needed state is contained in the compressor struct itself. |
| 6 | |
| 7 | use crate::deflate::core::{compress, CompressorOxide, TDEFLFlush, TDEFLStatus}; |
| 8 | use crate::{MZError, MZFlush, MZStatus, StreamResult}; |
| 9 | |
| 10 | /// Try to compress from input to output with the given [`CompressorOxide`]. |
| 11 | /// |
| 12 | /// # Errors |
| 13 | /// |
| 14 | /// Returns [`MZError::Buf`] If the size of the `output` slice is empty or no progress was made due |
| 15 | /// to lack of expected input data, or if called without [`MZFlush::Finish`] after the compression |
| 16 | /// was already finished. |
| 17 | /// |
| 18 | /// Returns [`MZError::Param`] if the compressor parameters are set wrong. |
| 19 | /// |
| 20 | /// Returns [`MZError::Stream`] when lower-level decompressor returns a |
| 21 | /// [`TDEFLStatus::PutBufFailed`]; may not actually be possible. |
| 22 | pub fn deflate( |
| 23 | compressor: &mut CompressorOxide, |
| 24 | input: &[u8], |
| 25 | output: &mut [u8], |
| 26 | flush: MZFlush, |
| 27 | ) -> StreamResult { |
| 28 | if output.is_empty() { |
| 29 | return StreamResult::error(MZError::Buf); |
| 30 | } |
| 31 | |
| 32 | if compressor.prev_return_status() == TDEFLStatus::Done { |
| 33 | return if flush == MZFlush::Finish { |
| 34 | StreamResult { |
| 35 | bytes_written: 0, |
| 36 | bytes_consumed: 0, |
| 37 | status: Ok(MZStatus::StreamEnd), |
| 38 | } |
| 39 | } else { |
| 40 | StreamResult::error(MZError::Buf) |
| 41 | }; |
| 42 | } |
| 43 | |
| 44 | let mut bytes_written = 0; |
| 45 | let mut bytes_consumed = 0; |
| 46 | |
| 47 | let mut next_in = input; |
| 48 | let mut next_out = output; |
| 49 | |
| 50 | let status = loop { |
| 51 | let in_bytes; |
| 52 | let out_bytes; |
| 53 | let defl_status = { |
| 54 | let res = compress(compressor, next_in, next_out, TDEFLFlush::from(flush)); |
| 55 | in_bytes = res.1; |
| 56 | out_bytes = res.2; |
| 57 | res.0 |
| 58 | }; |
| 59 | |
| 60 | next_in = &next_in[in_bytes..]; |
| 61 | next_out = &mut next_out[out_bytes..]; |
| 62 | bytes_consumed += in_bytes; |
| 63 | bytes_written += out_bytes; |
| 64 | |
| 65 | // Check if we are done, or compression failed. |
| 66 | match defl_status { |
| 67 | TDEFLStatus::BadParam => break Err(MZError::Param), |
| 68 | // Don't think this can happen as we're not using a custom callback. |
| 69 | TDEFLStatus::PutBufFailed => break Err(MZError::Stream), |
| 70 | TDEFLStatus::Done => break Ok(MZStatus::StreamEnd), |
| 71 | _ => (), |
| 72 | }; |
| 73 | |
| 74 | // All the output space was used, so wait for more. |
| 75 | if next_out.is_empty() { |
| 76 | break Ok(MZStatus::Ok); |
| 77 | } |
| 78 | |
| 79 | if next_in.is_empty() && (flush != MZFlush::Finish) { |
| 80 | let total_changed = bytes_written > 0 || bytes_consumed > 0; |
| 81 | |
| 82 | break if (flush != MZFlush::None) || total_changed { |
| 83 | // We wrote or consumed something, and/or did a flush (sync/partial etc.). |
| 84 | Ok(MZStatus::Ok) |
| 85 | } else { |
| 86 | // No more input data, not flushing, and nothing was consumed or written, |
| 87 | // so couldn't make any progress. |
| 88 | Err(MZError::Buf) |
| 89 | }; |
| 90 | } |
| 91 | }; |
| 92 | StreamResult { |
| 93 | bytes_consumed, |
| 94 | bytes_written, |
| 95 | status, |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | #[cfg (test)] |
| 100 | mod test { |
| 101 | use super::deflate; |
| 102 | use crate::deflate::CompressorOxide; |
| 103 | use crate::inflate::decompress_to_vec_zlib; |
| 104 | use crate::{MZFlush, MZStatus}; |
| 105 | use alloc::boxed::Box; |
| 106 | use alloc::vec; |
| 107 | |
| 108 | #[test ] |
| 109 | fn test_state() { |
| 110 | let data = b"Hello zlib!" ; |
| 111 | let mut compressed = vec![0; 50]; |
| 112 | let mut compressor = Box::<CompressorOxide>::default(); |
| 113 | let res = deflate(&mut compressor, data, &mut compressed, MZFlush::Finish); |
| 114 | let status = res.status.expect("Failed to compress!" ); |
| 115 | let decomp = |
| 116 | decompress_to_vec_zlib(&compressed).expect("Failed to decompress compressed data" ); |
| 117 | assert_eq!(status, MZStatus::StreamEnd); |
| 118 | assert_eq!(decomp[..], data[..]); |
| 119 | assert_eq!(res.bytes_consumed, data.len()); |
| 120 | } |
| 121 | } |
| 122 | |