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 pub fn as_bytes(&self) -> &[u8] {
49 &self.0
50 }
51}
52
53pub(crate) fn decompress_shard_body(data: Vec<u8>) -> Result<ShardBody> {
58 let data = if let Some(padding_size) = read_padding_info(&data) {
59 &data[..data.len().saturating_sub(16 + padding_size)]
60 } else {
61 &data[..]
62 };
63
64 if data.is_empty() {
65 return Ok(ShardBody(Vec::new()));
66 }
67
68 let decompressed = decompress_bounded(data, MAX_DECOMPRESSED_SIZE)?;
69 Ok(ShardBody(decompressed))
70}
71
72fn decompress_bounded(compressed: &[u8], max_size: u64) -> Result<Vec<u8>> {
82 use std::io::Read;
83
84 let mut decoder = zstd::Decoder::new(compressed)
85 .map_err(|e| VoidError::Compression(e.to_string()))?;
86
87 let initial_capacity = std::cmp::min(compressed.len().saturating_mul(4), max_size as usize);
88 let mut output = Vec::with_capacity(initial_capacity);
89
90 const CHUNK_SIZE: usize = 64 * 1024;
91 let mut buf = [0u8; CHUNK_SIZE];
92
93 loop {
94 let bytes_read = decoder
95 .read(&mut buf)
96 .map_err(|e| VoidError::Compression(e.to_string()))?;
97
98 if bytes_read == 0 {
99 break;
100 }
101
102 if output.len() + bytes_read > max_size as usize {
103 return Err(VoidError::Shard(format!(
104 "decompressed size exceeds maximum {} bytes",
105 max_size
106 )));
107 }
108
109 output.extend_from_slice(&buf[..bytes_read]);
110 }
111
112 Ok(output)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn decompress_bounded_stops_at_limit() {
121 let data = vec![0u8; 1024 * 1024];
122 let compressed = zstd::encode_all(&data[..], 3).unwrap();
123 let small_limit = 512 * 1024;
124
125 let result = decompress_bounded(&compressed, small_limit);
126 assert!(result.is_err());
127 assert!(result.unwrap_err().to_string().contains("exceeds maximum"));
128 }
129}