1use crate::compression::{ChunkedArchiveError, CompressionAlgorithm, ThreadLocalDecompressor};
6use std::ops::Range;
7use storage_ptr_slice::MutPtrByteSlice;
8
9pub trait DataBuffer: Send + 'static {
11 fn mut_ptr_slice(&mut self) -> MutPtrByteSlice<'_>;
13
14 fn commit(&mut self, size: usize) -> Result<(), ChunkedArchiveError>;
17}
18
19#[derive(Clone)]
20pub struct CompressionInfo {
21 chunk_size: u64,
22 compressed_size: u64,
23 small_offsets: Box<[u32]>,
26 large_offsets: Box<[u64]>,
27 decompressor: ThreadLocalDecompressor,
28}
29
30impl CompressionInfo {
31 pub fn new(
32 chunk_size: u64,
33 compressed_size: u64,
34 offsets: &[u64],
35 compression_algorithm: CompressionAlgorithm,
36 ) -> Result<Self, ChunkedArchiveError> {
37 let decompressor = compression_algorithm.thread_local_decompressor();
38 if chunk_size == 0 {
39 return Err(ChunkedArchiveError::IntegrityError);
40 } else if offsets.is_empty() || *offsets.first().unwrap() != 0 {
41 return Err(ChunkedArchiveError::IntegrityError);
43 } else if !offsets.array_windows().all(|[a, b]| a < b) {
44 return Err(ChunkedArchiveError::IntegrityError);
46 } else if offsets.len() == 1 {
47 Ok(Self {
50 chunk_size,
51 compressed_size,
52 small_offsets: Box::default(),
53 large_offsets: Box::default(),
54 decompressor,
55 })
56 } else if *offsets.last().unwrap() <= u32::MAX as u64 {
57 Ok(Self {
60 chunk_size,
61 compressed_size,
62 small_offsets: offsets[1..].iter().map(|x| *x as u32).collect(),
63 large_offsets: Box::default(),
64 decompressor,
65 })
66 } else {
67 let partition_point = offsets.partition_point(|&x| x <= u32::MAX as u64);
69 Ok(Self {
70 chunk_size,
71 compressed_size,
72 small_offsets: offsets[1..partition_point].iter().map(|x| *x as u32).collect(),
73 large_offsets: offsets[partition_point..].into(),
74 decompressor,
75 })
76 }
77 }
78
79 pub fn chunk_size(&self) -> u64 {
81 self.chunk_size
82 }
83
84 pub fn compressed_size(&self) -> u64 {
86 self.compressed_size
87 }
88
89 pub fn compressed_range_for_uncompressed_range(
91 &self,
92 range: &Range<u64>,
93 ) -> Result<Range<u64>, ChunkedArchiveError> {
94 if range.start % self.chunk_size != 0 || range.start >= range.end {
95 return Err(ChunkedArchiveError::IntegrityError);
96 }
97
98 let start_chunk_index = (range.start / self.chunk_size) as usize;
99 let start_offset = self
100 .compressed_offset_for_chunk_index(start_chunk_index)
101 .ok_or(ChunkedArchiveError::OutOfRange)?;
102
103 let end_chunk_index = range.end.div_ceil(self.chunk_size) as usize;
105 let end_offset = match self.compressed_offset_for_chunk_index(end_chunk_index) {
106 None => self.compressed_size,
107 Some(offset) => {
108 if !range.end.is_multiple_of(self.chunk_size) {
110 return Err(ChunkedArchiveError::IntegrityError);
111 }
112 offset
114 }
115 };
116
117 Ok(start_offset..end_offset)
118 }
119
120 fn compressed_offset_for_chunk_index(&self, chunk_index: usize) -> Option<u64> {
121 if chunk_index == 0 {
122 Some(0)
123 } else if chunk_index - 1 < self.small_offsets.len() {
124 Some(self.small_offsets[chunk_index - 1] as u64)
125 } else if chunk_index - 1 - self.small_offsets.len() < self.large_offsets.len() {
126 Some(self.large_offsets[chunk_index - 1 - self.small_offsets.len()])
127 } else {
128 None
129 }
130 }
131
132 pub fn decompress(
138 &self,
139 mut src: &[u8],
140 mut dst: &mut [u8],
141 dst_start_offset: u64,
142 ) -> Result<(), ChunkedArchiveError> {
143 if dst_start_offset % self.chunk_size != 0 {
144 return Err(ChunkedArchiveError::IntegrityError);
145 }
146
147 let start_chunk_index = (dst_start_offset / self.chunk_size) as usize;
148 let chunk_count = dst.len().div_ceil(self.chunk_size as usize);
149 let mut start_offset = self
150 .compressed_offset_for_chunk_index(start_chunk_index)
151 .ok_or(ChunkedArchiveError::IntegrityError)?;
152
153 for chunk_index in start_chunk_index..(start_chunk_index + chunk_count) {
155 match self.compressed_offset_for_chunk_index(chunk_index + 1) {
156 Some(end_offset) => {
157 let (to_decompress, src_remaining) = src
158 .split_at_checked((end_offset - start_offset) as usize)
159 .ok_or(ChunkedArchiveError::IntegrityError)?;
160 let (to_decompress_into, dst_remaining) = dst
161 .split_at_mut_checked(self.chunk_size as usize)
162 .ok_or(ChunkedArchiveError::IntegrityError)?;
163
164 let decompressed_bytes = self.decompressor.decompress_into(
165 to_decompress,
166 to_decompress_into,
167 chunk_index,
168 )?;
169 if decompressed_bytes != to_decompress_into.len() {
170 return Err(ChunkedArchiveError::IntegrityError);
171 }
172 src = src_remaining;
173 dst = dst_remaining;
174 start_offset = end_offset;
175 }
176 None => {
177 let decompressed_bytes =
178 self.decompressor.decompress_into(src, dst, chunk_index)?;
179 if decompressed_bytes != dst.len() {
180 return Err(ChunkedArchiveError::IntegrityError);
181 }
182 }
183 }
184 }
185
186 Ok(())
187 }
188
189 pub fn streaming_decompressor<'a, B: DataBuffer>(
194 &'a self,
195 range: Range<u64>,
196 uncompressed_size: u64,
197 dest_buf: B,
198 ) -> Result<(StreamingDecompressor<'a, B>, Range<u64>), ChunkedArchiveError> {
199 const BLOCK_SIZE: u64 = 4096;
200 let compressed = self.compressed_range_for_uncompressed_range(&range)?;
201 let aligned = (compressed.start / BLOCK_SIZE) * BLOCK_SIZE
202 ..compressed.end.next_multiple_of(BLOCK_SIZE);
203
204 let chunk_size = self.chunk_size();
205 assert_eq!(range.start % chunk_size, 0, "range.start must be chunk aligned");
206 let chunk_index = (range.start / chunk_size) as usize;
207
208 let decompressor = StreamingDecompressor {
209 info: self,
210 dest_buf,
211 range,
212 uncompressed_size,
213 chunk_index,
214 accumulator: Vec::new(),
215 current_compressed_offset: aligned.start,
216 failed: false,
217 };
218
219 Ok((decompressor, aligned))
220 }
221}
222
223pub struct StreamingDecompressor<'a, B> {
226 info: &'a CompressionInfo,
228
229 dest_buf: B,
231
232 range: Range<u64>,
234
235 uncompressed_size: u64,
237
238 chunk_index: usize,
240
241 accumulator: Vec<u8>,
243
244 current_compressed_offset: u64,
246
247 failed: bool,
250}
251
252impl<'a, B: DataBuffer> StreamingDecompressor<'a, B> {
253 pub fn push(&mut self, buffer_slice: &[u8]) -> Result<(), ChunkedArchiveError> {
256 if self.failed {
257 return Err(ChunkedArchiveError::IntegrityError);
258 }
259
260 if self.range.is_empty() {
261 return Ok(());
262 }
263
264 let buffer = self.current_compressed_offset
265 ..self.current_compressed_offset + buffer_slice.len() as u64;
266 self.current_compressed_offset = buffer.end;
267
268 let chunk_size = self.info.chunk_size();
269
270 while self.range.start < self.range.end {
271 let chunk_start = self
272 .info
273 .compressed_offset_for_chunk_index(self.chunk_index)
274 .ok_or(ChunkedArchiveError::OutOfRange)?;
275 let chunk_end = self
276 .info
277 .compressed_offset_for_chunk_index(self.chunk_index + 1)
278 .unwrap_or_else(|| self.info.compressed_size());
279 let chunk = chunk_start..chunk_end;
280
281 let decompress_chunk = |compressed_src: &[u8],
282 dest_buf: &mut B|
283 -> Result<(), ChunkedArchiveError> {
284 let mut dest_buffer = dest_buf.mut_ptr_slice().subslice_mut(0..chunk_size as usize);
285 let remaining = (self.uncompressed_size.saturating_sub(self.range.start)) as usize;
286 let chunk_uncompressed_len = if remaining < chunk_size as usize {
287 let (head, mut tail) = dest_buffer.split_at_mut(remaining);
289 tail.fill(0);
290 dest_buffer = head;
291 remaining
292 } else {
293 chunk_size as usize
294 };
295
296 let dst_slice = unsafe { &mut *dest_buffer.as_raw_mut_slice_ptr() };
299
300 let decompressed_bytes = self.info.decompressor.decompress_into(
301 compressed_src,
302 dst_slice,
303 self.chunk_index,
304 )?;
305 if decompressed_bytes != chunk_uncompressed_len {
306 return Err(ChunkedArchiveError::IntegrityError);
307 }
308 dest_buf.commit(chunk_uncompressed_len)?;
309 Ok(())
310 };
311
312 if chunk.start < buffer.start {
313 assert!(!self.accumulator.is_empty());
316 if chunk.end <= buffer.end {
317 let needed = (chunk.end - buffer.start) as usize;
318 self.accumulator.extend_from_slice(&buffer_slice[..needed]);
319 if let Err(e) = decompress_chunk(&self.accumulator, &mut self.dest_buf) {
320 self.failed = true;
321 return Err(e);
322 }
323 self.accumulator.clear();
324 self.range.start += chunk_size;
325 self.chunk_index += 1;
326 continue;
327 } else {
328 self.accumulator.extend_from_slice(buffer_slice);
329 break;
330 }
331 } else if chunk.end <= buffer.end {
332 let rel_start = (chunk.start - buffer.start) as usize;
334 let rel_end = (chunk.end - buffer.start) as usize;
335 let compressed_slice = &buffer_slice[rel_start..rel_end];
336
337 if let Err(e) = decompress_chunk(compressed_slice, &mut self.dest_buf) {
338 self.failed = true;
339 return Err(e);
340 }
341 self.range.start += chunk_size;
342 self.chunk_index += 1;
343 } else {
344 let rel_start = (chunk.start - buffer.start) as usize;
346 self.accumulator.extend_from_slice(&buffer_slice[rel_start..]);
347 break;
348 }
349 }
350 Ok(())
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_compression_info_new_small_and_large_offsets() {
360 let info = CompressionInfo::new(4096, 50, &[0], CompressionAlgorithm::Zstd).unwrap();
361 assert_eq!(info.chunk_size(), 4096);
362 assert_eq!(info.compressed_size(), 50);
363 assert_eq!(info.compressed_offset_for_chunk_index(0), Some(0));
364 assert_eq!(info.compressed_offset_for_chunk_index(1), None);
365
366 let info =
367 CompressionInfo::new(4096, 350, &[0, 100, 250], CompressionAlgorithm::Zstd).unwrap();
368 assert_eq!(info.compressed_offset_for_chunk_index(0), Some(0));
369 assert_eq!(info.compressed_offset_for_chunk_index(1), Some(100));
370 assert_eq!(info.compressed_offset_for_chunk_index(2), Some(250));
371 assert_eq!(info.compressed_offset_for_chunk_index(3), None);
372
373 let large_val = u32::MAX as u64 + 1000;
374 let info = CompressionInfo::new(
375 4096,
376 large_val + 500,
377 &[0, 500, large_val],
378 CompressionAlgorithm::Zstd,
379 )
380 .unwrap();
381 assert_eq!(info.compressed_offset_for_chunk_index(0), Some(0));
382 assert_eq!(info.compressed_offset_for_chunk_index(1), Some(500));
383 assert_eq!(info.compressed_offset_for_chunk_index(2), Some(large_val));
384 assert_eq!(info.compressed_offset_for_chunk_index(3), None);
385 }
386
387 #[test]
388 fn test_compressed_range_for_uncompressed_range() {
389 let info = CompressionInfo::new(4096, 500, &[0, 100, 250, 400], CompressionAlgorithm::Zstd)
390 .unwrap();
391 let range = info.compressed_range_for_uncompressed_range(&(0..4096)).unwrap();
392 assert_eq!(range, 0..100);
393
394 let range = info.compressed_range_for_uncompressed_range(&(4096..12288)).unwrap();
395 assert_eq!(range, 100..400);
396
397 let range = info.compressed_range_for_uncompressed_range(&(4096..16384)).unwrap();
398 assert_eq!(range, 100..500);
399 }
400
401 #[test]
402 fn test_compression_info_offsets_must_start_with_zero() {
403 assert!(CompressionInfo::new(4096, 100, &[], CompressionAlgorithm::Zstd).is_err());
404 assert!(CompressionInfo::new(4096, 100, &[1], CompressionAlgorithm::Zstd).is_err());
405 assert!(CompressionInfo::new(4096, 100, &[0], CompressionAlgorithm::Zstd).is_ok());
406 }
407
408 #[test]
409 fn test_compression_info_offsets_must_be_sorted() {
410 assert!(CompressionInfo::new(4096, 100, &[0, 1, 2], CompressionAlgorithm::Zstd).is_ok());
411 assert!(CompressionInfo::new(4096, 100, &[0, 2, 1], CompressionAlgorithm::Zstd).is_err());
412 assert!(CompressionInfo::new(4096, 100, &[0, 1, 1], CompressionAlgorithm::Zstd).is_err());
413 }
414
415 #[test]
416 fn test_compression_info_splitting_offsets() {
417 const MAX_SMALL_OFFSET: u64 = u32::MAX as u64;
418 let compression_info =
419 CompressionInfo::new(4096, 100, &[0], CompressionAlgorithm::Zstd).unwrap();
420 assert!(compression_info.small_offsets.is_empty());
421 assert!(compression_info.large_offsets.is_empty());
422
423 let compression_info =
424 CompressionInfo::new(4096, 20, &[0, 10], CompressionAlgorithm::Zstd).unwrap();
425 assert_eq!(&*compression_info.small_offsets, &[10]);
426 assert!(compression_info.large_offsets.is_empty());
427
428 let compression_info =
429 CompressionInfo::new(4096, 40, &[0, 10, 20, 30], CompressionAlgorithm::Zstd).unwrap();
430 assert_eq!(&*compression_info.small_offsets, &[10, 20, 30]);
431 assert!(compression_info.large_offsets.is_empty());
432
433 let compression_info = CompressionInfo::new(
434 4096,
435 MAX_SMALL_OFFSET,
436 &[0, MAX_SMALL_OFFSET - 1],
437 CompressionAlgorithm::Zstd,
438 )
439 .unwrap();
440 assert_eq!(&*compression_info.small_offsets, &[u32::MAX - 1]);
441 assert!(compression_info.large_offsets.is_empty());
442
443 let compression_info = CompressionInfo::new(
444 4096,
445 MAX_SMALL_OFFSET + 1,
446 &[0, MAX_SMALL_OFFSET],
447 CompressionAlgorithm::Zstd,
448 )
449 .unwrap();
450 assert_eq!(&*compression_info.small_offsets, &[u32::MAX]);
451 assert!(compression_info.large_offsets.is_empty());
452
453 let compression_info = CompressionInfo::new(
454 4096,
455 MAX_SMALL_OFFSET + 2,
456 &[0, MAX_SMALL_OFFSET + 1],
457 CompressionAlgorithm::Zstd,
458 )
459 .unwrap();
460 assert!(compression_info.small_offsets.is_empty());
461 assert_eq!(&*compression_info.large_offsets, &[MAX_SMALL_OFFSET + 1]);
462
463 let compression_info = CompressionInfo::new(
464 4096,
465 MAX_SMALL_OFFSET + 2,
466 &[0, MAX_SMALL_OFFSET - 1, MAX_SMALL_OFFSET, MAX_SMALL_OFFSET + 1],
467 CompressionAlgorithm::Zstd,
468 )
469 .unwrap();
470 assert_eq!(&*compression_info.small_offsets, &[u32::MAX - 1, u32::MAX]);
471 assert_eq!(&*compression_info.large_offsets, &[MAX_SMALL_OFFSET + 1]);
472
473 let compression_info = CompressionInfo::new(
474 4096,
475 MAX_SMALL_OFFSET + 20,
476 &[0, MAX_SMALL_OFFSET + 10],
477 CompressionAlgorithm::Zstd,
478 )
479 .unwrap();
480 assert!(compression_info.small_offsets.is_empty());
481 assert_eq!(&*compression_info.large_offsets, &[MAX_SMALL_OFFSET + 10]);
482
483 let compression_info = CompressionInfo::new(
484 4096,
485 MAX_SMALL_OFFSET + 30,
486 &[0, MAX_SMALL_OFFSET + 10, MAX_SMALL_OFFSET + 20],
487 CompressionAlgorithm::Zstd,
488 )
489 .unwrap();
490 assert!(compression_info.small_offsets.is_empty());
491 assert_eq!(
492 &*compression_info.large_offsets,
493 &[MAX_SMALL_OFFSET + 10, MAX_SMALL_OFFSET + 20]
494 );
495 }
496
497 struct TestBuffer {
498 data: Vec<u8>,
499 committed: usize,
500 }
501
502 impl TestBuffer {
503 fn new(size: usize) -> Self {
504 Self { data: vec![0u8; size], committed: 0 }
505 }
506 }
507
508 impl DataBuffer for TestBuffer {
509 fn mut_ptr_slice(&mut self) -> MutPtrByteSlice<'_> {
510 let slice = &mut self.data[self.committed..];
511 unsafe { MutPtrByteSlice::new(slice as *mut [u8]) }
512 }
513
514 fn commit(&mut self, size: usize) -> Result<(), ChunkedArchiveError> {
515 self.committed += size;
516 Ok(())
517 }
518 }
519
520 #[test]
521 fn test_streaming_decompressor_single_buffer() {
522 let uncompressed_data: Vec<u8> = (0..32768).map(|i| (i % 251) as u8).collect();
523 let options = crate::compression::ChunkedArchiveOptions::V3 {
524 compression_algorithm: CompressionAlgorithm::Zstd,
525 };
526 let archive = crate::compression::ChunkedArchive::new(&uncompressed_data, options).unwrap();
527
528 let mut compressed_offsets = vec![0];
529 let mut compressed_data = vec![];
530 for chunk in archive.chunks() {
531 compressed_data.extend_from_slice(&chunk.compressed_data);
532 compressed_offsets.push(compressed_data.len() as u64);
533 }
534 compressed_offsets.pop();
535
536 let info = CompressionInfo::new(
537 archive.chunk_size() as u64,
538 compressed_data.len() as u64,
539 &compressed_offsets,
540 CompressionAlgorithm::Zstd,
541 )
542 .unwrap();
543
544 let buf = TestBuffer::new(32768);
545 let (mut decompressor, aligned) =
546 info.streaming_decompressor(0..32768, 32768, buf).unwrap();
547 assert_eq!(aligned, 0..4096);
548
549 decompressor.push(&compressed_data).unwrap();
550 assert_eq!(&decompressor.dest_buf.data[..32768], &uncompressed_data[..]);
551 }
552
553 #[test]
554 fn test_streaming_decompressor_straddled_buffers() {
555 let uncompressed_data: Vec<u8> = (0..65536).map(|i| (i % 251) as u8).collect();
556 let options = crate::compression::ChunkedArchiveOptions::V3 {
557 compression_algorithm: CompressionAlgorithm::Zstd,
558 };
559 let archive = crate::compression::ChunkedArchive::new(&uncompressed_data, options).unwrap();
560
561 let mut compressed_offsets = vec![0];
562 let mut compressed_data = vec![];
563 for chunk in archive.chunks() {
564 compressed_data.extend_from_slice(&chunk.compressed_data);
565 compressed_offsets.push(compressed_data.len() as u64);
566 }
567 compressed_offsets.pop();
568
569 let info = CompressionInfo::new(
570 archive.chunk_size() as u64,
571 compressed_data.len() as u64,
572 &compressed_offsets,
573 CompressionAlgorithm::Zstd,
574 )
575 .unwrap();
576
577 let buf = TestBuffer::new(65536);
578 let (mut decompressor, _) = info.streaming_decompressor(0..65536, 65536, buf).unwrap();
579
580 for slice in compressed_data.chunks(10) {
581 decompressor.push(slice).unwrap();
582 }
583 assert_eq!(&decompressor.dest_buf.data[..65536], &uncompressed_data[..]);
584 }
585
586 #[test]
587 fn test_streaming_decompressor_partial_last_chunk_zero_tail() {
588 let uncompressed_size = 32768 + 1024;
589 let uncompressed_data: Vec<u8> = (0..uncompressed_size).map(|i| (i % 251) as u8).collect();
590
591 let options = crate::compression::ChunkedArchiveOptions::V3 {
592 compression_algorithm: CompressionAlgorithm::Zstd,
593 };
594 let archive = crate::compression::ChunkedArchive::new(&uncompressed_data, options).unwrap();
595
596 let mut compressed_offsets = vec![0];
597 let mut compressed_data = vec![];
598 for chunk in archive.chunks() {
599 compressed_data.extend_from_slice(&chunk.compressed_data);
600 compressed_offsets.push(compressed_data.len() as u64);
601 }
602 compressed_offsets.pop();
603
604 let info = CompressionInfo::new(
605 archive.chunk_size() as u64,
606 compressed_data.len() as u64,
607 &compressed_offsets,
608 CompressionAlgorithm::Zstd,
609 )
610 .unwrap();
611
612 let mut buf = TestBuffer::new(65536);
613 buf.data.fill(0xFF);
614
615 let (mut decompressor, _) = info
616 .streaming_decompressor(0..uncompressed_size as u64, uncompressed_size as u64, buf)
617 .unwrap();
618 decompressor.push(&compressed_data).unwrap();
619
620 assert_eq!(&decompressor.dest_buf.data[..uncompressed_size], &uncompressed_data[..]);
621 assert_eq!(&decompressor.dest_buf.data[uncompressed_size..65536], &[0u8; 31744]);
622 }
623
624 #[test]
625 fn test_streaming_decompressor_unaligned_start_returns_err() {
626 let info = CompressionInfo::new(4096, 500, &[0], CompressionAlgorithm::Zstd).unwrap();
627 let buf = TestBuffer::new(4096);
628 assert!(info.streaming_decompressor(100..4096, 4096, buf).is_err());
629 }
630
631 #[test]
632 fn test_streaming_decompressor_fused_error() {
633 let info = CompressionInfo::new(4096, 500, &[0], CompressionAlgorithm::Zstd).unwrap();
634 let buf = TestBuffer::new(4096);
635 let (mut decompressor, _) = info.streaming_decompressor(0..4096, 4096, buf).unwrap();
636
637 let invalid_compressed_data = vec![0xFFu8; 4096];
638 assert!(decompressor.push(&invalid_compressed_data).is_err());
639 assert!(decompressor.push(&invalid_compressed_data).is_err());
640 }
641}