void_core/shard/
reader.rs1use super::writer::read_padding_info;
4use crate::metadata::ManifestEntry;
5use crate::{FileContent, Result, VoidError};
6
7const MAX_DECOMPRESSED_SIZE: u64 = 1024 * 1024 * 1024;
12
13pub struct ShardBody(Vec<u8>);
18
19impl ShardBody {
20 pub fn read_file(&self, entry: &ManifestEntry) -> Result<FileContent> {
24 let start = entry.offset as usize;
25 let end = start
26 .checked_add(entry.length as usize)
27 .ok_or_else(|| VoidError::Shard("offset+length overflow".into()))?;
28 if end > self.0.len() {
29 return Err(VoidError::Shard(format!(
30 "file '{}' range {}..{} exceeds body size {}",
31 entry.path, start, end, self.0.len()
32 )));
33 }
34 Ok(self.0[start..end].to_vec())
35 }
36
37 pub fn len(&self) -> usize {
39 self.0.len()
40 }
41
42 pub fn is_empty(&self) -> bool {
44 self.0.is_empty()
45 }
46}
47
48pub(crate) fn decompress_shard_body(data: Vec<u8>) -> Result<ShardBody> {
53 let data = if let Some(padding_size) = read_padding_info(&data) {
54 &data[..data.len().saturating_sub(16 + padding_size)]
55 } else {
56 &data[..]
57 };
58
59 if data.is_empty() {
60 return Ok(ShardBody(Vec::new()));
61 }
62
63 let decompressed = decompress_bounded(data, MAX_DECOMPRESSED_SIZE)?;
64 Ok(ShardBody(decompressed))
65}
66
67fn decompress_bounded(compressed: &[u8], max_size: u64) -> Result<Vec<u8>> {
77 use std::io::Read;
78
79 let mut decoder = zstd::Decoder::new(compressed)
80 .map_err(|e| VoidError::Compression(e.to_string()))?;
81
82 let initial_capacity = std::cmp::min(compressed.len().saturating_mul(4), max_size as usize);
83 let mut output = Vec::with_capacity(initial_capacity);
84
85 const CHUNK_SIZE: usize = 64 * 1024;
86 let mut buf = [0u8; CHUNK_SIZE];
87
88 loop {
89 let bytes_read = decoder
90 .read(&mut buf)
91 .map_err(|e| VoidError::Compression(e.to_string()))?;
92
93 if bytes_read == 0 {
94 break;
95 }
96
97 if output.len() + bytes_read > max_size as usize {
98 return Err(VoidError::Shard(format!(
99 "decompressed size exceeds maximum {} bytes",
100 max_size
101 )));
102 }
103
104 output.extend_from_slice(&buf[..bytes_read]);
105 }
106
107 Ok(output)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn decompress_bounded_stops_at_limit() {
116 let data = vec![0u8; 1024 * 1024];
117 let compressed = zstd::encode_all(&data[..], 3).unwrap();
118 let small_limit = 512 * 1024;
119
120 let result = decompress_bounded(&compressed, small_limit);
121 assert!(result.is_err());
122 assert!(result.unwrap_err().to_string().contains("exceeds maximum"));
123 }
124}