1use crate::deflate_scan::{self, FlushBoundary};
16use crate::entry::ZipError;
17
18pub struct ChunkSplit {
20 pub segment_starts: Vec<usize>,
22 pub decode_segments: usize,
24 pub consumed: usize,
26}
27
28pub fn split_chunk(data: &[u8], n_workers: usize, is_last: bool) -> Option<ChunkSplit> {
36 let boundaries: Vec<FlushBoundary> =
37 deflate_scan::split_boundaries_parallel(data, n_workers);
38
39 if boundaries.is_empty() {
40 return None;
41 }
42
43 let mut segment_starts = Vec::with_capacity(boundaries.len() + 1);
45 segment_starts.push(0usize);
46 for b in &boundaries {
47 if *segment_starts.last().unwrap() != b.start {
48 segment_starts.push(b.start);
49 }
50 }
51
52 let n = segment_starts.len();
53 let decode_segments = if is_last {
54 n
55 } else if n > 1 {
56 n - 1
57 } else {
58 return None;
59 };
60
61 let consumed = if decode_segments < n {
62 segment_starts[decode_segments]
63 } else {
64 data.len()
65 };
66
67 Some(ChunkSplit { segment_starts, decode_segments, consumed })
68}
69
70pub fn decode_segment_into(
77 compressed: &[u8],
78 output: &mut Vec<u8>,
79) -> Result<(), ZipError> {
80 let headroom = linflate::OVERWRITE_HEADROOM;
81 let out_start = output.len();
82 let mut buf_size = (compressed.len() * 4).max(4096) + headroom;
83
84 loop {
85 output.reserve(buf_size);
86 let spare_len = output.capacity() - out_start;
87 let out_buf = unsafe {
88 std::slice::from_raw_parts_mut(output.as_mut_ptr().add(out_start), spare_len)
89 };
90
91 match linflate::inflate_segment(compressed, out_buf) {
92 Ok(written) => {
93 unsafe { output.set_len(out_start + written) };
94 return Ok(());
95 }
96 Err(linflate::InflateError::OutputOverflow) => {
97 buf_size = buf_size.saturating_mul(2);
98 if buf_size > 4 * 1024 * 1024 * 1024 {
99 return Err(ZipError("DEFLATE segment too large"));
100 }
101 }
102 Err(_) => return Err(ZipError("DEFLATE segment decompression failed")),
103 }
104 }
105}
106
107pub fn decode_segment(compressed: &[u8]) -> Result<Vec<u8>, ZipError> {
109 let mut out = Vec::new();
110 decode_segment_into(compressed, &mut out)?;
111 Ok(out)
112}
113
114pub fn decode_chunk(
125 data: &[u8],
126 n_workers: usize,
127 is_last: bool,
128) -> Result<(Vec<Vec<u8>>, usize), ZipError> {
129 let split = split_chunk(data, n_workers, is_last)
130 .ok_or(ZipError("no full-flush boundaries — fallback to single-core"))?;
131
132 let segments: Vec<&[u8]> = (0..split.decode_segments)
133 .map(|i| {
134 let start = split.segment_starts[i];
135 let end = if i + 1 < split.segment_starts.len() {
136 split.segment_starts[i + 1]
137 } else {
138 split.consumed
139 };
140 &data[start..end]
141 })
142 .collect();
143
144 let outputs: Vec<Result<Vec<u8>, ZipError>> =
145 gatling::gatling_forkjoin::gatling_for_each(segments.len(), 0, |i| decode_segment(segments[i]));
146
147 let mut result = Vec::with_capacity(outputs.len());
148 for r in outputs {
149 result.push(r?);
150 }
151
152 Ok((result, split.consumed))
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 fn stored_block(payload: &[u8], bfinal: bool) -> Vec<u8> {
163 let mut out = Vec::new();
165 let header: u8 = if bfinal { 0x01 } else { 0x00 };
166 out.push(header);
167 let len = payload.len() as u16;
168 out.extend_from_slice(&len.to_le_bytes());
169 out.extend_from_slice(&(!len).to_le_bytes());
170 out.extend_from_slice(payload);
171 out
172 }
173
174 fn flush_marker() -> Vec<u8> {
175 vec![0x00, 0x00, 0x00, 0xFF, 0xFF]
177 }
178
179 #[test]
182 fn split_chunk_no_boundaries_returns_none() {
183 let data: Vec<u8> = (0u8..=255).cycle().take(128).collect();
185 assert!(split_chunk(&data, 4, true).is_none());
186 }
187
188 #[test]
189 fn split_chunk_single_boundary_is_last() {
190 let payload_a = vec![0xAA_u8; 15];
196 let mut buf = stored_block(&payload_a, false); buf.extend_from_slice(&flush_marker()); buf.extend_from_slice(&stored_block(b"world", true)); let split = split_chunk(&buf, 2, true).expect("should find boundary");
201 assert!(split.decode_segments >= 1);
202 assert_eq!(split.consumed, buf.len());
203 }
204
205 #[test]
206 fn split_chunk_single_boundary_not_last() {
207 let mut buf = stored_block(b"hello", false);
211 buf.extend_from_slice(&flush_marker());
212 buf.extend_from_slice(&stored_block(b"world", false));
213
214 let result = split_chunk(&buf, 2, false);
215 if let Some(s) = result {
216 assert!(s.consumed <= buf.len());
217 assert!(s.decode_segments >= 1);
218 }
219 }
221
222 #[test]
225 fn decode_segment_stored_final() {
226 let data = stored_block(b"Hello, World!\n", true);
227 let out = decode_segment(&data).expect("decode stored block");
228 assert_eq!(out, b"Hello, World!\n");
229 }
230
231 #[test]
232 fn decode_segment_stored_non_final() {
233 let data = stored_block(b"partial segment", false);
235 let out = decode_segment(&data).expect("decode non-final stored block");
236 assert_eq!(out, b"partial segment");
237 }
238
239 #[test]
240 fn decode_segment_real_deflate() {
241 use miniz_oxide::deflate::compress_to_vec;
243 let original = b"The quick brown fox jumps over the lazy dog".repeat(50);
244 let compressed = compress_to_vec(&original, 6);
245 let out = decode_segment(&compressed).expect("decode real deflate");
246 assert_eq!(out, original.to_vec());
247 }
248
249 #[test]
252 fn decode_chunk_no_boundaries_returns_err() {
253 let data = stored_block(b"no flush here", true);
255 assert!(decode_chunk(&data, 4, true).is_err());
256 }
257
258 #[test]
259 fn decode_chunk_two_segments() {
260 let payload_a = vec![0xAA_u8; 22]; let payload_b = vec![0xBB_u8; 5]; let mut buf = stored_block(&payload_a, false);
267 buf.extend_from_slice(&flush_marker());
268 buf.extend_from_slice(&stored_block(&payload_b, true));
269
270 let (outputs, consumed) = decode_chunk(&buf, 2, true).expect("decode two segments");
271 assert_eq!(consumed, buf.len());
272 let combined: Vec<u8> = outputs.into_iter().flatten().collect();
273 let expected: Vec<u8> = payload_a.iter().chain(payload_b.iter()).copied().collect();
274 assert_eq!(combined, expected);
275 }
276}