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