1 use super::{stream::FormatErrorInner, DecodingError, CHUNCK_BUFFER_SIZE}; 2 3 use miniz_oxide::inflate::core::{decompress, inflate_flags, DecompressorOxide}; 4 use miniz_oxide::inflate::TINFLStatus; 5 6 /// Ergonomics wrapper around `miniz_oxide::inflate::stream` for zlib compressed data. 7 pub(super) struct ZlibStream { 8 /// Current decoding state. 9 state: Box<DecompressorOxide>, 10 /// If there has been a call to decompress already. 11 started: bool, 12 /// A buffer of compressed data. 13 /// We use this for a progress guarantee. The data in the input stream is chunked as given by 14 /// the underlying stream buffer. We will not read any more data until the current buffer has 15 /// been fully consumed. The zlib decompression can not fully consume all the data when it is 16 /// in the middle of the stream, it will treat full symbols and maybe the last bytes need to be 17 /// treated in a special way. The exact reason isn't as important but the interface does not 18 /// promise us this. Now, the complication is that the _current_ chunking information of PNG 19 /// alone is not enough to determine this as indeed the compressed stream is the concatenation 20 /// of all consecutive `IDAT`/`fdAT` chunks. We would need to inspect the next chunk header. 21 /// 22 /// Thus, there needs to be a buffer that allows fully clearing a chunk so that the next chunk 23 /// type can be inspected. 24 in_buffer: Vec<u8>, 25 /// The logical start of the `in_buffer`. 26 in_pos: usize, 27 /// Remaining buffered decoded bytes. 28 /// The decoder sometimes wants inspect some already finished bytes for further decoding. So we 29 /// keep a total of 32KB of decoded data available as long as more data may be appended. 30 out_buffer: Vec<u8>, 31 /// The cursor position in the output stream as a buffer index. 32 out_pos: usize, 33 } 34 35 impl ZlibStream { new() -> Self36 pub(crate) fn new() -> Self { 37 ZlibStream { 38 state: Box::default(), 39 started: false, 40 in_buffer: Vec::with_capacity(CHUNCK_BUFFER_SIZE), 41 in_pos: 0, 42 out_buffer: vec![0; 2 * CHUNCK_BUFFER_SIZE], 43 out_pos: 0, 44 } 45 } 46 reset(&mut self)47 pub(crate) fn reset(&mut self) { 48 self.started = false; 49 self.in_buffer.clear(); 50 self.out_buffer.clear(); 51 self.out_pos = 0; 52 *self.state = DecompressorOxide::default(); 53 } 54 55 /// Fill the decoded buffer as far as possible from `data`. 56 /// On success returns the number of consumed input bytes. decompress( &mut self, data: &[u8], image_data: &mut Vec<u8>, ) -> Result<usize, DecodingError>57 pub(crate) fn decompress( 58 &mut self, 59 data: &[u8], 60 image_data: &mut Vec<u8>, 61 ) -> Result<usize, DecodingError> { 62 const BASE_FLAGS: u32 = inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER 63 | inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF 64 | inflate_flags::TINFL_FLAG_HAS_MORE_INPUT; 65 66 self.prepare_vec_for_appending(); 67 68 let (status, mut in_consumed, out_consumed) = { 69 let in_data = if self.in_buffer.is_empty() { 70 data 71 } else { 72 &self.in_buffer[self.in_pos..] 73 }; 74 decompress( 75 &mut self.state, 76 in_data, 77 &mut self.out_buffer.as_mut_slice(), 78 self.out_pos, 79 BASE_FLAGS, 80 ) 81 }; 82 83 if !self.in_buffer.is_empty() { 84 self.in_pos += in_consumed; 85 } 86 87 if self.in_buffer.len() == self.in_pos { 88 self.in_buffer.clear(); 89 self.in_pos = 0; 90 } 91 92 if in_consumed == 0 { 93 self.in_buffer.extend_from_slice(data); 94 in_consumed = data.len(); 95 } 96 97 self.started = true; 98 self.out_pos += out_consumed; 99 self.transfer_finished_data(image_data); 100 101 match status { 102 TINFLStatus::Done | TINFLStatus::HasMoreOutput | TINFLStatus::NeedsMoreInput => { 103 Ok(in_consumed) 104 } 105 err => Err(DecodingError::Format( 106 FormatErrorInner::CorruptFlateStream { err }.into(), 107 )), 108 } 109 } 110 111 /// Called after all consecutive IDAT chunks were handled. 112 /// 113 /// The compressed stream can be split on arbitrary byte boundaries. This enables some cleanup 114 /// within the decompressor and flushing additional data which may have been kept back in case 115 /// more data were passed to it. finish_compressed_chunks( &mut self, image_data: &mut Vec<u8>, ) -> Result<(), DecodingError>116 pub(crate) fn finish_compressed_chunks( 117 &mut self, 118 image_data: &mut Vec<u8>, 119 ) -> Result<(), DecodingError> { 120 const BASE_FLAGS: u32 = inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER 121 | inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; 122 123 if !self.started { 124 return Ok(()); 125 } 126 127 let tail = self.in_buffer.split_off(0); 128 let tail = &tail[self.in_pos..]; 129 130 let mut start = 0; 131 loop { 132 self.prepare_vec_for_appending(); 133 134 let (status, in_consumed, out_consumed) = { 135 // TODO: we may be able to avoid the indirection through the buffer here. 136 // First append all buffered data and then create a cursor on the image_data 137 // instead. 138 decompress( 139 &mut self.state, 140 &tail[start..], 141 &mut self.out_buffer.as_mut_slice(), 142 self.out_pos, 143 BASE_FLAGS, 144 ) 145 }; 146 147 start += in_consumed; 148 self.out_pos += out_consumed; 149 150 match status { 151 TINFLStatus::Done => { 152 self.out_buffer.truncate(self.out_pos as usize); 153 image_data.append(&mut self.out_buffer); 154 return Ok(()); 155 } 156 TINFLStatus::HasMoreOutput => { 157 let transferred = self.transfer_finished_data(image_data); 158 assert!( 159 transferred > 0 || in_consumed > 0 || out_consumed > 0, 160 "No more forward progress made in stream decoding." 161 ); 162 } 163 err => { 164 return Err(DecodingError::Format( 165 FormatErrorInner::CorruptFlateStream { err }.into(), 166 )); 167 } 168 } 169 } 170 } 171 172 /// Resize the vector to allow allocation of more data. prepare_vec_for_appending(&mut self)173 fn prepare_vec_for_appending(&mut self) { 174 if self.out_buffer.len().saturating_sub(self.out_pos) >= CHUNCK_BUFFER_SIZE { 175 return; 176 } 177 178 let buffered_len = self.decoding_size(self.out_buffer.len()); 179 debug_assert!(self.out_buffer.len() <= buffered_len); 180 self.out_buffer.resize(buffered_len, 0u8); 181 } 182 decoding_size(&self, len: usize) -> usize183 fn decoding_size(&self, len: usize) -> usize { 184 // Allocate one more chunk size than currently or double the length while ensuring that the 185 // allocation is valid and that any cursor within it will be valid. 186 len 187 // This keeps the buffer size a power-of-two, required by miniz_oxide. 188 .saturating_add(CHUNCK_BUFFER_SIZE.max(len)) 189 // Ensure all buffer indices are valid cursor positions. 190 // Note: both cut off and zero extension give correct results. 191 .min(u64::max_value() as usize) 192 // Ensure the allocation request is valid. 193 // TODO: maximum allocation limits? 194 .min(isize::max_value() as usize) 195 } 196 transfer_finished_data(&mut self, image_data: &mut Vec<u8>) -> usize197 fn transfer_finished_data(&mut self, image_data: &mut Vec<u8>) -> usize { 198 let safe = self.out_pos.saturating_sub(CHUNCK_BUFFER_SIZE); 199 // TODO: allocation limits. 200 image_data.extend(self.out_buffer.drain(..safe)); 201 self.out_pos -= safe; 202 safe 203 } 204 } 205