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