1use crate::error::{Error, Result};
12use crate::header::{MAGIC_SHARD, SHARD_HEADER_LEN};
13use crate::util::{Cursor, Writer};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ShardHeader {
18 pub file_uuid: [u8; 16],
20 pub shard_index: u32,
22 pub shard_total: u32,
24 pub data_offset: u64,
26 pub data_len: u64,
28 pub crc32: u32,
30 pub redundant_header_len: u32,
32}
33
34impl ShardHeader {
35 pub fn parse(data: &[u8]) -> Result<Self> {
42 if data.len() < SHARD_HEADER_LEN {
43 return Err(Error::Truncated {
44 context: "shard header",
45 need: SHARD_HEADER_LEN,
46 got: data.len(),
47 });
48 }
49 let mut c = Cursor::new(data, "shard header");
50 let magic: [u8; 8] = c.take_array()?;
51 if magic != MAGIC_SHARD {
52 return Err(Error::BadMagic { got: magic });
53 }
54 let h = Self {
55 file_uuid: c.take_array()?,
56 shard_index: c.u32le()?,
57 shard_total: c.u32le()?,
58 data_offset: c.u64le()?,
59 data_len: c.u64le()?,
60 crc32: c.u32le()?,
61 redundant_header_len: c.u32le()?,
62 };
63 debug_assert_eq!(c.position(), SHARD_HEADER_LEN, "分片头布局与规范不符");
64 Ok(h)
65 }
66
67 pub fn to_bytes(&self) -> Result<[u8; SHARD_HEADER_LEN]> {
73 let mut w = Writer::with_capacity(SHARD_HEADER_LEN);
74 w.bytes(&MAGIC_SHARD)
75 .bytes(&self.file_uuid)
76 .u32le(self.shard_index)
77 .u32le(self.shard_total)
78 .u64le(self.data_offset)
79 .u64le(self.data_len)
80 .u32le(self.crc32)
81 .u32le(self.redundant_header_len);
82
83 let v = w.into_vec();
84 let mut out = [0u8; SHARD_HEADER_LEN];
85 let src = v.get(..SHARD_HEADER_LEN).ok_or(Error::MalformedHeader {
86 reason: "serialized shard header shorter than 56 bytes",
87 })?;
88 out.copy_from_slice(src);
89 Ok(out)
90 }
91
92 #[must_use]
94 pub const fn data_start(&self) -> usize {
95 SHARD_HEADER_LEN.saturating_add(self.redundant_header_len as usize)
96 }
97}
98
99#[must_use]
101pub fn crc32(data: &[u8]) -> u32 {
102 let mut h = crc32fast::Hasher::new();
103 h.update(data);
104 h.finalize()
105}
106
107pub fn split(
116 full_ciphertext: &[u8],
117 file_uuid: &[u8; 16],
118 shard_size: usize,
119 redundant_header: Option<&[u8]>,
120) -> Result<Vec<Vec<u8>>> {
121 if shard_size == 0 {
122 return Err(Error::MalformedHeader { reason: "shard_size must not be zero" });
123 }
124 let total = full_ciphertext.len().div_ceil(shard_size).max(1);
125 let total_u32 = u32::try_from(total)
126 .map_err(|_| Error::MalformedHeader { reason: "shard count exceeds u32" })?;
127
128 let mut shards = Vec::with_capacity(total);
129 for i in 0..total {
130 let start = i.saturating_mul(shard_size);
131 let end = start.saturating_add(shard_size).min(full_ciphertext.len());
132 let data = full_ciphertext.get(start..end).unwrap_or(&[]);
133
134 let i_u32 = u32::try_from(i)
135 .map_err(|_| Error::MalformedHeader { reason: "shard index exceeds u32" })?;
136 let redundant = if i_u32 == 0 { None } else { redundant_header };
138 let rlen = redundant.map_or(0, <[u8]>::len);
139
140 let sh = ShardHeader {
141 file_uuid: *file_uuid,
142 shard_index: i_u32,
143 shard_total: total_u32,
144 data_offset: start as u64,
145 data_len: data.len() as u64,
146 crc32: crc32(data),
147 redundant_header_len: u32::try_from(rlen).map_err(|_| Error::MalformedHeader {
148 reason: "redundant header exceeds u32",
149 })?,
150 };
151
152 let mut w = Writer::with_capacity(SHARD_HEADER_LEN.saturating_add(rlen).saturating_add(data.len()));
153 w.bytes(&sh.to_bytes()?);
154 if let Some(r) = redundant {
155 w.bytes(r);
156 }
157 w.bytes(data);
158 shards.push(w.into_vec());
159 }
160 Ok(shards)
161}
162
163pub fn merge(shards: &[Vec<u8>]) -> Result<Vec<u8>> {
175 if shards.is_empty() {
176 return Err(Error::MissingShards { missing: Vec::new(), total: 0 });
177 }
178
179 let mut parsed: Vec<(ShardHeader, &[u8])> = Vec::with_capacity(shards.len());
180 let mut expect_uuid: Option<[u8; 16]> = None;
181 let mut expect_total: Option<u32> = None;
182
183 for raw in shards {
184 let h = ShardHeader::parse(raw)?;
185
186 match expect_uuid {
187 None => expect_uuid = Some(h.file_uuid),
188 Some(u) if u != h.file_uuid => {
189 return Err(Error::ShardUuidMismatch { index: h.shard_index });
190 }
191 Some(_) => {}
192 }
193 if expect_total.is_none() {
194 expect_total = Some(h.shard_total);
195 }
196
197 let start = h.data_start();
198 let len = usize::try_from(h.data_len).map_err(|_| Error::MalformedHeader {
199 reason: "shard data_len exceeds addressable range",
200 })?;
201 let end = start.saturating_add(len);
202 let data = raw.get(start..end).ok_or(Error::Truncated {
203 context: "shard data",
204 need: end,
205 got: raw.len(),
206 })?;
207
208 let computed = crc32(data);
209 if computed != h.crc32 {
210 return Err(Error::ShardCrcMismatch {
211 index: h.shard_index,
212 expected: h.crc32,
213 computed,
214 });
215 }
216 parsed.push((h, data));
217 }
218
219 let total = expect_total.unwrap_or(0);
220 parsed.sort_by_key(|(h, _)| h.shard_index);
221
222 let present: Vec<u32> = parsed.iter().map(|(h, _)| h.shard_index).collect();
224 let missing: Vec<u32> = (0..total).filter(|i| !present.contains(i)).collect();
225 if !missing.is_empty() {
226 return Err(Error::MissingShards { missing, total });
227 }
228
229 let cap = parsed.iter().map(|(_, d)| d.len()).sum();
230 let mut out = Vec::with_capacity(cap);
231 for (_, d) in &parsed {
232 out.extend_from_slice(d);
233 }
234 Ok(out)
235}
236
237pub fn recover_header_from_shard(shard: &[u8]) -> Result<Vec<u8>> {
247 let h = ShardHeader::parse(shard)?;
248 if h.redundant_header_len == 0 {
249 return Err(Error::MalformedHeader {
250 reason: "this shard carries no redundant header",
251 });
252 }
253 let len = usize::try_from(h.redundant_header_len).map_err(|_| Error::MalformedHeader {
254 reason: "redundant_header_len exceeds addressable range",
255 })?;
256 let end = SHARD_HEADER_LEN.saturating_add(len);
257 let hdr = shard.get(SHARD_HEADER_LEN..end).ok_or(Error::Truncated {
258 context: "redundant header",
259 need: end,
260 got: shard.len(),
261 })?;
262 Ok(hdr.to_vec())
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
267pub struct ShardCoverage {
268 pub total: u32,
270 pub missing: Vec<u32>,
272 pub holes: Vec<(u64, u64)>,
274}
275
276impl ShardCoverage {
277 #[must_use]
279 pub fn is_readable(&self, offset: u64, length: u64) -> bool {
280 let end = offset.saturating_add(length);
281 !self.holes.iter().any(|&(ho, hl)| {
282 let hend = ho.saturating_add(hl);
283 offset < hend && ho < end
284 })
285 }
286}
287
288pub fn analyze_coverage(shards: &[Vec<u8>]) -> Result<ShardCoverage> {
294 let mut heads = Vec::with_capacity(shards.len());
295 for raw in shards {
296 heads.push(ShardHeader::parse(raw)?);
297 }
298 let total = heads.first().map_or(0, |h| h.shard_total);
299 let present: Vec<u32> = heads.iter().map(|h| h.shard_index).collect();
300 let missing: Vec<u32> = (0..total).filter(|i| !present.contains(i)).collect();
301
302 let shard_size = heads
304 .iter()
305 .filter(|h| h.shard_index.saturating_add(1) < h.shard_total)
306 .map(|h| h.data_len)
307 .max()
308 .or_else(|| heads.first().map(|h| h.data_len))
309 .unwrap_or(0);
310
311 let holes = missing
312 .iter()
313 .map(|&i| (u64::from(i).saturating_mul(shard_size), shard_size))
314 .collect();
315
316 Ok(ShardCoverage { total, missing, holes })
317}