1use crc32c::crc32c;
2
3use crate::crypto::KdfParams;
4use crate::format::{
5 AeadAlgo, BlockKind, CompressionAlgo, FecAlgo, FormatError, KdfAlgo, BLOCK_RECORD_FRAMING_LEN,
6 BOOTSTRAP_SIDECAR_HEADER_LEN, CRITICAL_METADATA_IMAGE_FIXED_LEN,
7 CRITICAL_METADATA_RECOVERY_HEADER_LEN, CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN,
8 CRITICAL_RECOVERY_LOCATOR_LEN, CRYPTO_EXTENSION_HEADER_LEN, CRYPTO_EXTENSION_MAX_VALUE_LEN,
9 CRYPTO_HEADER_FIXED_LEN, CRYPTO_HEADER_HMAC_LEN, FORMAT_VERSION, IMAGE_CRC_LEN,
10 MANIFEST_FOOTER_LEN, READER_MAX_BLOCK_SIZE, READER_MAX_CHUNK_SIZE,
11 READER_MAX_CRYPTO_HEADER_LEN, READER_MAX_ENVELOPE_TARGET_SIZE, READER_MAX_FEC_CLASS_SHARDS,
12 READER_MAX_INDEX_FEC_CLASS_SHARDS, READER_MAX_INDEX_ROOT_FEC_CLASS_SHARDS,
13 READER_MAX_PATH_LENGTH, READER_MAX_ROOT_AUTH_AUTHENTICATOR_VALUE_LEN,
14 READER_MAX_ROOT_AUTH_FOOTER_LEN, READER_MAX_ROOT_AUTH_SIGNER_IDENTITY_LEN,
15 READER_MAX_STRIPE_WIDTH, ROOT_AUTH_FOOTER_FIXED_LEN, ROOT_AUTH_SPEC_ID,
16 SERIALIZED_REGION_HEADER_LEN, VOLUME_FORMAT_REV, VOLUME_HEADER_LEN, VOLUME_TRAILER_LEN,
17};
18use crate::raw_stream_profile::{
19 validate_raw_stream_content_model_extension, RAW_STREAM_CONTENT_MODEL_EXTENSION_TAG,
20};
21
22const TZAP_MAGIC: [u8; 4] = *b"TZAP";
23const TZCH_MAGIC: [u8; 4] = *b"TZCH";
24const TZBK_MAGIC: [u8; 4] = *b"TZBK";
25const TZMF_MAGIC: [u8; 4] = *b"TZMF";
26const TZVT_MAGIC: [u8; 4] = *b"TZVT";
27const TZRA_MAGIC: [u8; 4] = *b"TZRA";
28const TZBS_MAGIC: [u8; 4] = *b"TZBS";
29const TZMI_MAGIC: [u8; 4] = *b"TZMI";
30const TZCR_MAGIC: [u8; 4] = *b"TZCR";
31const TZCS_MAGIC: [u8; 4] = *b"TZCS";
32const TZCL_MAGIC: [u8; 4] = *b"TZCL";
33
34const BLOCK_LAST_DATA_FLAG: u8 = 0x01;
35const BLOCK_RESERVED_FLAGS: u8 = !BLOCK_LAST_DATA_FLAG;
36
37const SIDECAR_MANIFEST_PRESENT: u32 = 0x01;
38const SIDECAR_INDEX_ROOT_PRESENT: u32 = 0x02;
39const SIDECAR_DICTIONARY_PRESENT: u32 = 0x04;
40const SIDECAR_KNOWN_FLAGS: u32 =
41 SIDECAR_MANIFEST_PRESENT | SIDECAR_INDEX_ROOT_PRESENT | SIDECAR_DICTIONARY_PRESENT;
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct VolumeHeader {
45 pub format_version: u16,
46 pub volume_format_rev: u16,
47 pub volume_index: u32,
48 pub stripe_width: u32,
49 pub archive_uuid: [u8; 16],
50 pub session_id: [u8; 16],
51 pub crypto_header_offset: u32,
52 pub crypto_header_length: u32,
53 pub header_crc32c: u32,
54}
55
56impl VolumeHeader {
57 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
58 expect_len("VolumeHeader", VOLUME_HEADER_LEN, bytes.len())?;
59 expect_magic("VolumeHeader", TZAP_MAGIC, &bytes[0..4])?;
60 expect_crc("VolumeHeader", &bytes[..124], read_u32(bytes, 124)?)?;
61 expect_zero("VolumeHeader", &bytes[56..124])?;
62
63 let header = Self {
64 format_version: read_u16(bytes, 4)?,
65 volume_format_rev: read_u16(bytes, 6)?,
66 volume_index: read_u32(bytes, 8)?,
67 stripe_width: read_u32(bytes, 12)?,
68 archive_uuid: read_array_16(bytes, 16)?,
69 session_id: read_array_16(bytes, 32)?,
70 crypto_header_offset: read_u32(bytes, 48)?,
71 crypto_header_length: read_u32(bytes, 52)?,
72 header_crc32c: read_u32(bytes, 124)?,
73 };
74 header.validate()?;
75 Ok(header)
76 }
77
78 pub fn validate(&self) -> Result<(), FormatError> {
79 if self.format_version != FORMAT_VERSION {
80 return Err(FormatError::UnsupportedFormatVersion(self.format_version));
81 }
82 if self.volume_format_rev != VOLUME_FORMAT_REV {
83 return Err(FormatError::UnsupportedVolumeFormatRevision(
84 self.volume_format_rev,
85 ));
86 }
87 if self.stripe_width == 0 {
88 return Err(FormatError::ZeroStripeWidth);
89 }
90 if self.volume_index >= self.stripe_width {
91 return Err(FormatError::VolumeIndexOutOfRange {
92 volume_index: self.volume_index,
93 stripe_width: self.stripe_width,
94 });
95 }
96 if self.crypto_header_offset != VOLUME_HEADER_LEN as u32 {
97 return Err(FormatError::NonCanonicalCryptoHeaderOffset(
98 self.crypto_header_offset,
99 ));
100 }
101 if self.crypto_header_length > READER_MAX_CRYPTO_HEADER_LEN {
102 return Err(FormatError::ReaderResourceLimitExceeded {
103 field: "CryptoHeader length",
104 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
105 actual: self.crypto_header_length as u64,
106 });
107 }
108 Ok(())
109 }
110
111 pub fn to_bytes(&self) -> [u8; VOLUME_HEADER_LEN] {
112 let mut bytes = [0u8; VOLUME_HEADER_LEN];
113 bytes[0..4].copy_from_slice(&TZAP_MAGIC);
114 write_u16(&mut bytes, 4, self.format_version);
115 write_u16(&mut bytes, 6, self.volume_format_rev);
116 write_u32(&mut bytes, 8, self.volume_index);
117 write_u32(&mut bytes, 12, self.stripe_width);
118 bytes[16..32].copy_from_slice(&self.archive_uuid);
119 bytes[32..48].copy_from_slice(&self.session_id);
120 write_u32(&mut bytes, 48, self.crypto_header_offset);
121 write_u32(&mut bytes, 52, self.crypto_header_length);
122 let crc = crc32c(&bytes[..124]);
123 write_u32(&mut bytes, 124, crc);
124 bytes
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct CryptoHeaderFixed {
130 pub length: u32,
131 pub compression_algo: CompressionAlgo,
132 pub aead_algo: AeadAlgo,
133 pub fec_algo: FecAlgo,
134 pub kdf_algo: KdfAlgo,
135 pub chunk_size: u32,
136 pub envelope_target_size: u32,
137 pub block_size: u32,
138 pub fec_data_shards: u16,
139 pub fec_parity_shards: u16,
140 pub index_fec_data_shards: u16,
141 pub index_fec_parity_shards: u16,
142 pub index_root_fec_data_shards: u16,
143 pub index_root_fec_parity_shards: u16,
144 pub stripe_width: u32,
145 pub volume_loss_tolerance: u8,
146 pub bit_rot_buffer_pct: u8,
147 pub has_dictionary: u8,
148 pub max_path_length: u32,
149 pub expected_volume_size: u64,
150}
151
152impl CryptoHeaderFixed {
153 pub fn parse(bytes: &[u8], volume_crypto_header_length: u32) -> Result<Self, FormatError> {
154 expect_len("CryptoHeaderFixed", CRYPTO_HEADER_FIXED_LEN, bytes.len())?;
155 expect_magic("CryptoHeaderFixed", TZCH_MAGIC, &bytes[0..4])?;
156 expect_zero("CryptoHeaderFixed", &bytes[47..48])?;
157 expect_zero("CryptoHeaderFixed", &bytes[60..76])?;
158
159 let length = read_u32(bytes, 4)?;
160 if length != volume_crypto_header_length {
161 return Err(FormatError::CryptoHeaderLengthMismatch {
162 fixed: length,
163 volume: volume_crypto_header_length,
164 });
165 }
166 if length > READER_MAX_CRYPTO_HEADER_LEN {
167 return Err(FormatError::ReaderResourceLimitExceeded {
168 field: "CryptoHeader length",
169 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
170 actual: length as u64,
171 });
172 }
173
174 let header = Self {
175 length,
176 compression_algo: CompressionAlgo::try_from(read_u16(bytes, 8)?)?,
177 aead_algo: AeadAlgo::try_from(read_u16(bytes, 10)?)?,
178 fec_algo: FecAlgo::try_from(read_u16(bytes, 12)?)?,
179 kdf_algo: KdfAlgo::try_from(read_u16(bytes, 14)?)?,
180 chunk_size: read_u32(bytes, 16)?,
181 envelope_target_size: read_u32(bytes, 20)?,
182 block_size: read_u32(bytes, 24)?,
183 fec_data_shards: read_u16(bytes, 28)?,
184 fec_parity_shards: read_u16(bytes, 30)?,
185 index_fec_data_shards: read_u16(bytes, 32)?,
186 index_fec_parity_shards: read_u16(bytes, 34)?,
187 index_root_fec_data_shards: read_u16(bytes, 36)?,
188 index_root_fec_parity_shards: read_u16(bytes, 38)?,
189 stripe_width: read_u32(bytes, 40)?,
190 volume_loss_tolerance: bytes[44],
191 bit_rot_buffer_pct: bytes[45],
192 has_dictionary: bytes[46],
193 max_path_length: read_u32(bytes, 48)?,
194 expected_volume_size: read_u64(bytes, 52)?,
195 };
196 header.validate_supported_profile()?;
197 Ok(header)
198 }
199
200 pub fn validate_supported_profile(&self) -> Result<(), FormatError> {
201 if self.compression_algo != CompressionAlgo::ZstdFramed {
202 return Err(FormatError::UnsupportedCompression(self.compression_algo));
203 }
204 if self.fec_algo != FecAlgo::ReedSolomonGF16 {
205 return Err(FormatError::UnsupportedFec(self.fec_algo));
206 }
207 if self.has_dictionary > 1 {
208 return Err(FormatError::InvalidBoolean {
209 field: "has_dictionary",
210 value: self.has_dictionary,
211 });
212 }
213 if self.stripe_width == 0 {
214 return Err(FormatError::ZeroStripeWidth);
215 }
216 if self.stripe_width > READER_MAX_STRIPE_WIDTH {
217 return Err(FormatError::ReaderResourceLimitExceeded {
218 field: "stripe_width",
219 cap: READER_MAX_STRIPE_WIDTH as u64,
220 actual: self.stripe_width as u64,
221 });
222 }
223 if self.volume_loss_tolerance as u32 >= self.stripe_width {
224 return Err(FormatError::VolumeLossToleranceOutOfRange {
225 volume_loss_tolerance: self.volume_loss_tolerance,
226 stripe_width: self.stripe_width,
227 });
228 }
229 if self.bit_rot_buffer_pct > 100 {
230 return Err(FormatError::BitRotBufferPctTooLarge(
231 self.bit_rot_buffer_pct,
232 ));
233 }
234 if self.fec_data_shards == 0 {
235 return Err(FormatError::ZeroDataShardMaximum {
236 field: "fec_data_shards",
237 });
238 }
239 if self.index_fec_data_shards == 0 {
240 return Err(FormatError::ZeroDataShardMaximum {
241 field: "index_fec_data_shards",
242 });
243 }
244 if self.index_root_fec_data_shards == 0 {
245 return Err(FormatError::ZeroDataShardMaximum {
246 field: "index_root_fec_data_shards",
247 });
248 }
249 validate_fec_class_shards(
250 "fec_data_shards + fec_parity_shards",
251 self.fec_data_shards,
252 self.fec_parity_shards,
253 READER_MAX_FEC_CLASS_SHARDS,
254 )?;
255 validate_fec_class_shards(
256 "index_fec_data_shards + index_fec_parity_shards",
257 self.index_fec_data_shards,
258 self.index_fec_parity_shards,
259 READER_MAX_INDEX_FEC_CLASS_SHARDS,
260 )?;
261 validate_fec_class_shards(
262 "index_root_fec_data_shards + index_root_fec_parity_shards",
263 self.index_root_fec_data_shards,
264 self.index_root_fec_parity_shards,
265 READER_MAX_INDEX_ROOT_FEC_CLASS_SHARDS,
266 )?;
267 if self.chunk_size == 0 {
268 return Err(FormatError::ZeroChunkSize);
269 }
270 if self.envelope_target_size == 0 {
271 return Err(FormatError::ZeroEnvelopeTargetSize);
272 }
273 if self.chunk_size > self.envelope_target_size {
274 return Err(FormatError::ChunkSizeExceedsEnvelopeTarget {
275 chunk_size: self.chunk_size,
276 envelope_target_size: self.envelope_target_size,
277 });
278 }
279 if self.chunk_size > READER_MAX_CHUNK_SIZE {
280 return Err(FormatError::ReaderResourceLimitExceeded {
281 field: "chunk_size",
282 cap: READER_MAX_CHUNK_SIZE as u64,
283 actual: self.chunk_size as u64,
284 });
285 }
286 if self.envelope_target_size > READER_MAX_ENVELOPE_TARGET_SIZE {
287 return Err(FormatError::ReaderResourceLimitExceeded {
288 field: "envelope_target_size",
289 cap: READER_MAX_ENVELOPE_TARGET_SIZE as u64,
290 actual: self.envelope_target_size as u64,
291 });
292 }
293 if self.block_size < 4096 {
294 return Err(FormatError::BlockSizeTooSmall(self.block_size));
295 }
296 if self.block_size % 2 != 0 {
297 return Err(FormatError::OddBlockSize(self.block_size));
298 }
299 validate_fec_class_data_shards("fec_data_shards", self.fec_data_shards, self.block_size)?;
300 validate_fec_class_data_shards(
301 "index_fec_data_shards",
302 self.index_fec_data_shards,
303 self.block_size,
304 )?;
305 validate_fec_class_data_shards(
306 "index_root_fec_data_shards",
307 self.index_root_fec_data_shards,
308 self.block_size,
309 )?;
310 if self.block_size > READER_MAX_BLOCK_SIZE {
311 return Err(FormatError::ReaderResourceLimitExceeded {
312 field: "block_size",
313 cap: READER_MAX_BLOCK_SIZE as u64,
314 actual: self.block_size as u64,
315 });
316 }
317 if self.max_path_length > READER_MAX_PATH_LENGTH {
318 return Err(FormatError::ReaderResourceLimitExceeded {
319 field: "max_path_length",
320 cap: READER_MAX_PATH_LENGTH as u64,
321 actual: self.max_path_length as u64,
322 });
323 }
324 Ok(())
325 }
326
327 pub fn to_bytes(&self) -> [u8; CRYPTO_HEADER_FIXED_LEN] {
328 let mut bytes = [0u8; CRYPTO_HEADER_FIXED_LEN];
329 bytes[0..4].copy_from_slice(&TZCH_MAGIC);
330 write_u32(&mut bytes, 4, self.length);
331 write_u16(&mut bytes, 8, self.compression_algo as u16);
332 write_u16(&mut bytes, 10, self.aead_algo as u16);
333 write_u16(&mut bytes, 12, self.fec_algo as u16);
334 write_u16(&mut bytes, 14, self.kdf_algo as u16);
335 write_u32(&mut bytes, 16, self.chunk_size);
336 write_u32(&mut bytes, 20, self.envelope_target_size);
337 write_u32(&mut bytes, 24, self.block_size);
338 write_u16(&mut bytes, 28, self.fec_data_shards);
339 write_u16(&mut bytes, 30, self.fec_parity_shards);
340 write_u16(&mut bytes, 32, self.index_fec_data_shards);
341 write_u16(&mut bytes, 34, self.index_fec_parity_shards);
342 write_u16(&mut bytes, 36, self.index_root_fec_data_shards);
343 write_u16(&mut bytes, 38, self.index_root_fec_parity_shards);
344 write_u32(&mut bytes, 40, self.stripe_width);
345 bytes[44] = self.volume_loss_tolerance;
346 bytes[45] = self.bit_rot_buffer_pct;
347 bytes[46] = self.has_dictionary;
348 write_u32(&mut bytes, 48, self.max_path_length);
349 write_u64(&mut bytes, 52, self.expected_volume_size);
350 bytes
351 }
352}
353
354fn validate_fec_class_shards(
355 field: &'static str,
356 data_shards: u16,
357 parity_shards: u16,
358 cap: u32,
359) -> Result<(), FormatError> {
360 let total = data_shards as u32 + parity_shards as u32;
361 if total > cap {
362 return Err(FormatError::ReaderResourceLimitExceeded {
363 field,
364 cap: cap as u64,
365 actual: total as u64,
366 });
367 }
368 Ok(())
369}
370
371fn validate_fec_class_data_shards(
372 field: &'static str,
373 data_shards: u16,
374 block_size: u32,
375) -> Result<(), FormatError> {
376 let max_data_shards = u32::MAX as u64 / block_size as u64;
377 if (data_shards as u64) > max_data_shards {
378 return Err(FormatError::ReaderResourceLimitExceeded {
379 field,
380 cap: max_data_shards,
381 actual: data_shards as u64,
382 });
383 }
384 Ok(())
385}
386
387#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct ExtensionTlv<'a> {
389 pub tag: u16,
390 pub value: &'a [u8],
391}
392
393pub fn scan_crypto_extension_tlvs(bytes: &[u8]) -> Result<Vec<ExtensionTlv<'_>>, FormatError> {
394 let mut offset = 0usize;
395 let mut extensions = Vec::new();
396 loop {
397 if offset == bytes.len() {
398 return Err(FormatError::MissingExtensionTerminator);
399 }
400 if bytes.len() - offset < CRYPTO_EXTENSION_HEADER_LEN {
401 return Err(FormatError::TruncatedExtensionHeader);
402 }
403 let tag = read_u16(bytes, offset)?;
404 let length = read_u32(bytes, offset + 2)?;
405 offset += CRYPTO_EXTENSION_HEADER_LEN;
406 if tag == 0 {
407 if length != 0 {
408 return Err(FormatError::MalformedExtensionTerminator);
409 }
410 if offset != bytes.len() {
411 return Err(FormatError::BytesAfterExtensionTerminator);
412 }
413 return Ok(extensions);
414 }
415 if length > CRYPTO_EXTENSION_MAX_VALUE_LEN {
416 return Err(FormatError::ExtensionPayloadTooLarge(length));
417 }
418 let length = length as usize;
419 if bytes.len() - offset < length {
420 return Err(FormatError::TruncatedExtensionPayload);
421 }
422 extensions.push(ExtensionTlv {
423 tag,
424 value: &bytes[offset..offset + length],
425 });
426 offset += length;
427 }
428}
429
430pub fn validate_crypto_extension_semantics(
431 extensions: &[ExtensionTlv<'_>],
432) -> Result<(), FormatError> {
433 let mut seen_known = Vec::new();
434 for extension in extensions {
435 let ext_tag = extension.tag & 0x7fff;
436 let is_critical = extension.tag & 0x8000 != 0;
437 if matches!(ext_tag, 0x0004 | 0x0006) {
438 return Err(FormatError::ForbiddenExtensionTag(ext_tag));
439 }
440 if ext_tag == RAW_STREAM_CONTENT_MODEL_EXTENSION_TAG {
441 if is_critical {
442 if seen_known.contains(&ext_tag) {
443 return Err(FormatError::DuplicateKnownExtension(ext_tag));
444 }
445 validate_raw_stream_content_model_extension(is_critical, extension.value)?;
446 seen_known.push(ext_tag);
447 }
448 continue;
449 }
450 if is_known_extension(ext_tag) {
451 if seen_known.contains(&ext_tag) {
452 return Err(FormatError::DuplicateKnownExtension(ext_tag));
453 }
454 validate_known_extension(ext_tag, extension.value)?;
455 seen_known.push(ext_tag);
456 } else if is_critical {
457 return Err(FormatError::UnknownCriticalExtension(ext_tag));
458 }
459 }
460 Ok(())
461}
462
463#[derive(Debug, Clone, PartialEq, Eq)]
464pub struct CryptoHeader<'a> {
465 pub fixed: CryptoHeaderFixed,
466 pub kdf_params: KdfParams,
467 pub extensions: Vec<ExtensionTlv<'a>>,
468 pub header_hmac: [u8; 32],
469 pub hmac_covered_bytes: &'a [u8],
470}
471
472impl<'a> CryptoHeader<'a> {
473 pub fn parse(bytes: &'a [u8], volume_crypto_header_length: u32) -> Result<Self, FormatError> {
474 let declared_len = volume_crypto_header_length as usize;
475 if volume_crypto_header_length > READER_MAX_CRYPTO_HEADER_LEN {
476 return Err(FormatError::ReaderResourceLimitExceeded {
477 field: "CryptoHeader length",
478 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
479 actual: volume_crypto_header_length as u64,
480 });
481 }
482 if bytes.len() != declared_len {
483 return Err(FormatError::InvalidLength {
484 structure: "CryptoHeader",
485 expected: declared_len,
486 actual: bytes.len(),
487 });
488 }
489 let min_len =
490 CRYPTO_HEADER_FIXED_LEN + 2 + CRYPTO_EXTENSION_HEADER_LEN + CRYPTO_HEADER_HMAC_LEN;
491 if bytes.len() < min_len {
492 return Err(FormatError::CryptoHeaderTooShort {
493 min: min_len,
494 actual: bytes.len(),
495 });
496 }
497
498 let fixed = CryptoHeaderFixed::parse(
499 &bytes[..CRYPTO_HEADER_FIXED_LEN],
500 volume_crypto_header_length,
501 )?;
502 let hmac_offset = bytes.len() - CRYPTO_HEADER_HMAC_LEN;
503 let (kdf_params, kdf_len) =
504 KdfParams::parse(fixed.kdf_algo, &bytes[CRYPTO_HEADER_FIXED_LEN..hmac_offset])?;
505 let extension_bytes = &bytes[CRYPTO_HEADER_FIXED_LEN + kdf_len..hmac_offset];
506 let extensions = scan_crypto_extension_tlvs(extension_bytes)?;
507 let header_hmac = read_array_32(bytes, hmac_offset)?;
508
509 Ok(Self {
510 fixed,
511 kdf_params,
512 extensions,
513 header_hmac,
514 hmac_covered_bytes: &bytes[..hmac_offset],
515 })
516 }
517
518 pub fn validate_extension_semantics(&self) -> Result<(), FormatError> {
519 validate_crypto_extension_semantics(&self.extensions)
520 }
521}
522
523#[derive(Debug, Clone, PartialEq, Eq)]
524pub struct BlockRecord {
525 pub block_index: u64,
526 pub kind: BlockKind,
527 pub flags: u8,
528 pub payload: Vec<u8>,
529 pub record_crc32c: u32,
530}
531
532impl BlockRecord {
533 pub fn parse(bytes: &[u8], block_size: usize) -> Result<Self, FormatError> {
534 let expected = block_size + BLOCK_RECORD_FRAMING_LEN;
535 expect_len("BlockRecord", expected, bytes.len())?;
536 expect_magic("BlockRecord", TZBK_MAGIC, &bytes[0..4])?;
537 expect_zero("BlockRecord", &bytes[14..16])?;
538 expect_crc(
539 "BlockRecord",
540 &bytes[..16 + block_size],
541 read_u32(bytes, 16 + block_size)?,
542 )?;
543
544 let kind = BlockKind::try_from(bytes[12])?;
545 let flags = bytes[13];
546 if flags & BLOCK_RESERVED_FLAGS != 0 {
547 return Err(FormatError::InvalidBlockFlags(flags));
548 }
549 if kind.is_parity() && flags & BLOCK_LAST_DATA_FLAG != 0 {
550 return Err(FormatError::ParityBlockHasLastDataFlag);
551 }
552
553 Ok(Self {
554 block_index: read_u64(bytes, 4)?,
555 kind,
556 flags,
557 payload: bytes[16..16 + block_size].to_vec(),
558 record_crc32c: read_u32(bytes, 16 + block_size)?,
559 })
560 }
561
562 pub fn is_last_data(&self) -> bool {
563 self.flags & BLOCK_LAST_DATA_FLAG != 0
564 }
565
566 pub fn to_bytes(&self) -> Vec<u8> {
567 let mut bytes = vec![0u8; self.payload.len() + BLOCK_RECORD_FRAMING_LEN];
568 bytes[0..4].copy_from_slice(&TZBK_MAGIC);
569 write_u64(&mut bytes, 4, self.block_index);
570 bytes[12] = self.kind as u8;
571 bytes[13] = self.flags;
572 bytes[16..16 + self.payload.len()].copy_from_slice(&self.payload);
573 let crc = crc32c(&bytes[..16 + self.payload.len()]);
574 let crc_offset = 16 + self.payload.len();
575 write_u32(&mut bytes, crc_offset, crc);
576 bytes
577 }
578}
579
580#[derive(Debug, Clone, PartialEq, Eq)]
581pub struct ManifestFooter {
582 pub archive_uuid: [u8; 16],
583 pub session_id: [u8; 16],
584 pub volume_index: u32,
585 pub is_authoritative: u8,
586 pub total_volumes: u32,
587 pub index_root_first_block: u64,
588 pub index_root_data_block_count: u32,
589 pub index_root_parity_block_count: u32,
590 pub index_root_encrypted_size: u32,
591 pub index_root_decompressed_size: u32,
592 pub manifest_hmac: [u8; 32],
593}
594
595impl ManifestFooter {
596 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
597 expect_len("ManifestFooter", MANIFEST_FOOTER_LEN, bytes.len())?;
598 expect_magic("ManifestFooter", TZMF_MAGIC, &bytes[0..4])?;
599 expect_zero("ManifestFooter", &bytes[41..44])?;
600 expect_zero("ManifestFooter", &bytes[72..104])?;
601 let is_authoritative = bytes[40];
602 if is_authoritative > 1 {
603 return Err(FormatError::InvalidAuthoritativeFlag(is_authoritative));
604 }
605
606 Ok(Self {
607 archive_uuid: read_array_16(bytes, 4)?,
608 session_id: read_array_16(bytes, 20)?,
609 volume_index: read_u32(bytes, 36)?,
610 is_authoritative,
611 total_volumes: read_u32(bytes, 44)?,
612 index_root_first_block: read_u64(bytes, 48)?,
613 index_root_data_block_count: read_u32(bytes, 56)?,
614 index_root_parity_block_count: read_u32(bytes, 60)?,
615 index_root_encrypted_size: read_u32(bytes, 64)?,
616 index_root_decompressed_size: read_u32(bytes, 68)?,
617 manifest_hmac: read_array_32(bytes, 104)?,
618 })
619 }
620
621 pub fn validate_index_root_extent(&self, block_size: u32) -> Result<(), FormatError> {
622 if self.index_root_data_block_count == 0 || self.index_root_encrypted_size == 0 {
623 return Err(FormatError::EmptyIndexRootExtent);
624 }
625 let expected = self
626 .index_root_data_block_count
627 .checked_mul(block_size)
628 .ok_or(FormatError::IndexRootSizeMismatch)?;
629 if expected != self.index_root_encrypted_size {
630 return Err(FormatError::IndexRootSizeMismatch);
631 }
632 Ok(())
633 }
634
635 pub fn to_bytes(&self) -> [u8; MANIFEST_FOOTER_LEN] {
636 let mut bytes = [0u8; MANIFEST_FOOTER_LEN];
637 bytes[0..4].copy_from_slice(&TZMF_MAGIC);
638 bytes[4..20].copy_from_slice(&self.archive_uuid);
639 bytes[20..36].copy_from_slice(&self.session_id);
640 write_u32(&mut bytes, 36, self.volume_index);
641 bytes[40] = self.is_authoritative;
642 write_u32(&mut bytes, 44, self.total_volumes);
643 write_u64(&mut bytes, 48, self.index_root_first_block);
644 write_u32(&mut bytes, 56, self.index_root_data_block_count);
645 write_u32(&mut bytes, 60, self.index_root_parity_block_count);
646 write_u32(&mut bytes, 64, self.index_root_encrypted_size);
647 write_u32(&mut bytes, 68, self.index_root_decompressed_size);
648 bytes[104..136].copy_from_slice(&self.manifest_hmac);
649 bytes
650 }
651}
652
653#[derive(Debug, Clone, PartialEq, Eq)]
654pub struct VolumeTrailer {
655 pub archive_uuid: [u8; 16],
656 pub session_id: [u8; 16],
657 pub volume_index: u32,
658 pub block_count: u64,
659 pub bytes_written: u64,
660 pub manifest_footer_offset: u64,
661 pub manifest_footer_length: u32,
662 pub closed_at_ns: i64,
663 pub root_auth_footer_offset: u64,
664 pub root_auth_footer_length: u32,
665 pub root_auth_flags: u32,
666 pub trailer_hmac: [u8; 32],
667}
668
669impl VolumeTrailer {
670 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
671 expect_len("VolumeTrailer", VOLUME_TRAILER_LEN, bytes.len())?;
672 expect_magic("VolumeTrailer", TZVT_MAGIC, &bytes[0..4])?;
673 expect_zero("VolumeTrailer", &bytes[92..96])?;
674 let manifest_footer_length = read_u32(bytes, 64)?;
675 if manifest_footer_length != MANIFEST_FOOTER_LEN as u32 {
676 return Err(FormatError::InvalidManifestFooterLength(
677 manifest_footer_length,
678 ));
679 }
680 let root_auth_flags = read_u32(bytes, 88)?;
681 if root_auth_flags & !0x0000_0001 != 0 {
682 return Err(FormatError::InvalidArchive(
683 "VolumeTrailer root_auth_flags has unknown bits",
684 ));
685 }
686
687 Ok(Self {
688 archive_uuid: read_array_16(bytes, 4)?,
689 session_id: read_array_16(bytes, 20)?,
690 volume_index: read_u32(bytes, 36)?,
691 block_count: read_u64(bytes, 40)?,
692 bytes_written: read_u64(bytes, 48)?,
693 manifest_footer_offset: read_u64(bytes, 56)?,
694 manifest_footer_length,
695 closed_at_ns: read_i64(bytes, 68)?,
696 root_auth_footer_offset: read_u64(bytes, 76)?,
697 root_auth_footer_length: read_u32(bytes, 84)?,
698 root_auth_flags,
699 trailer_hmac: read_array_32(bytes, 96)?,
700 })
701 }
702
703 pub fn to_bytes(&self) -> [u8; VOLUME_TRAILER_LEN] {
704 let mut bytes = [0u8; VOLUME_TRAILER_LEN];
705 bytes[0..4].copy_from_slice(&TZVT_MAGIC);
706 bytes[4..20].copy_from_slice(&self.archive_uuid);
707 bytes[20..36].copy_from_slice(&self.session_id);
708 write_u32(&mut bytes, 36, self.volume_index);
709 write_u64(&mut bytes, 40, self.block_count);
710 write_u64(&mut bytes, 48, self.bytes_written);
711 write_u64(&mut bytes, 56, self.manifest_footer_offset);
712 write_u32(&mut bytes, 64, self.manifest_footer_length);
713 write_i64(&mut bytes, 68, self.closed_at_ns);
714 write_u64(&mut bytes, 76, self.root_auth_footer_offset);
715 write_u32(&mut bytes, 84, self.root_auth_footer_length);
716 write_u32(&mut bytes, 88, self.root_auth_flags);
717 bytes[96..128].copy_from_slice(&self.trailer_hmac);
718 bytes
719 }
720}
721
722#[derive(Debug, Clone, PartialEq, Eq)]
723pub struct RootAuthFooterV1 {
724 pub archive_uuid: [u8; 16],
725 pub session_id: [u8; 16],
726 pub authenticator_id: u16,
727 pub signer_identity_type: u16,
728 pub signer_identity_bytes: Vec<u8>,
729 pub authenticator_value: Vec<u8>,
730 pub total_data_block_count: u64,
731 pub critical_metadata_digest: [u8; 32],
732 pub index_digest: [u8; 32],
733 pub fec_layout_digest: [u8; 32],
734 pub data_block_merkle_root: [u8; 32],
735 pub signer_identity_digest: [u8; 32],
736 pub archive_root: [u8; 32],
737 pub footer_crc32c: u32,
738}
739
740impl RootAuthFooterV1 {
741 pub fn footer_length(&self) -> Result<u32, FormatError> {
742 root_auth_footer_length(
743 self.signer_identity_bytes.len(),
744 self.authenticator_value.len(),
745 )
746 }
747
748 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
749 validate_root_auth_variable_lengths(
750 self.signer_identity_bytes.len(),
751 self.authenticator_value.len(),
752 )?;
753 let footer_length = self.footer_length()?;
754 let mut bytes = vec![0u8; footer_length as usize];
755 bytes[0..4].copy_from_slice(&TZRA_MAGIC);
756 write_u16(&mut bytes, 4, 1);
757 bytes[6..30].copy_from_slice(&ROOT_AUTH_SPEC_ID);
758 write_u32(&mut bytes, 30, footer_length);
759 write_u32(&mut bytes, 34, 0);
760 bytes[38..54].copy_from_slice(&self.archive_uuid);
761 bytes[54..70].copy_from_slice(&self.session_id);
762 write_u16(&mut bytes, 70, FORMAT_VERSION);
763 write_u16(&mut bytes, 72, VOLUME_FORMAT_REV);
764 write_u16(&mut bytes, 74, self.authenticator_id);
765 write_u16(&mut bytes, 76, self.signer_identity_type);
766 write_u32(
767 &mut bytes,
768 78,
769 u32::try_from(self.signer_identity_bytes.len()).map_err(|_| {
770 FormatError::InvalidArchive("RootAuthFooterV1 signer identity length overflow")
771 })?,
772 );
773 write_u32(
774 &mut bytes,
775 82,
776 u32::try_from(self.authenticator_value.len()).map_err(|_| {
777 FormatError::InvalidArchive("RootAuthFooterV1 authenticator length overflow")
778 })?,
779 );
780 write_u64(&mut bytes, 86, self.total_data_block_count);
781 bytes[94..126].copy_from_slice(&self.critical_metadata_digest);
782 bytes[126..158].copy_from_slice(&self.index_digest);
783 bytes[158..190].copy_from_slice(&self.fec_layout_digest);
784 bytes[190..222].copy_from_slice(&self.data_block_merkle_root);
785 bytes[222..254].copy_from_slice(&self.signer_identity_digest);
786 bytes[254..286].copy_from_slice(&self.archive_root);
787 let signer_start = ROOT_AUTH_FOOTER_FIXED_LEN;
788 let signer_end = signer_start + self.signer_identity_bytes.len();
789 bytes[signer_start..signer_end].copy_from_slice(&self.signer_identity_bytes);
790 let auth_end = signer_end + self.authenticator_value.len();
791 bytes[signer_end..auth_end].copy_from_slice(&self.authenticator_value);
792 let crc = crc32c(&bytes[..auth_end]);
793 write_u32(&mut bytes, auth_end, crc);
794 Ok(bytes)
795 }
796
797 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
798 if bytes.len() > READER_MAX_ROOT_AUTH_FOOTER_LEN as usize {
799 return Err(FormatError::ReaderResourceLimitExceeded {
800 field: "RootAuthFooterV1 length",
801 cap: READER_MAX_ROOT_AUTH_FOOTER_LEN as u64,
802 actual: bytes.len() as u64,
803 });
804 }
805 let min_len = ROOT_AUTH_FOOTER_FIXED_LEN + 4;
806 if bytes.len() < min_len {
807 return Err(FormatError::InvalidLength {
808 structure: "RootAuthFooterV1",
809 expected: min_len,
810 actual: bytes.len(),
811 });
812 }
813 expect_magic("RootAuthFooterV1", TZRA_MAGIC, &bytes[0..4])?;
814 let version = read_u16(bytes, 4)?;
815 if version != 1 {
816 return Err(FormatError::UnsupportedFormatVersion(version));
817 }
818 if read_array_24(bytes, 6)? != ROOT_AUTH_SPEC_ID {
819 return Err(FormatError::InvalidArchive(
820 "RootAuthFooterV1 root_auth_spec_id is unsupported",
821 ));
822 }
823 let footer_length = read_u32(bytes, 30)?;
824 if footer_length as usize != bytes.len() {
825 return Err(FormatError::InvalidLength {
826 structure: "RootAuthFooterV1",
827 expected: footer_length as usize,
828 actual: bytes.len(),
829 });
830 }
831 if read_u32(bytes, 34)? != 0 {
832 return Err(FormatError::InvalidArchive(
833 "RootAuthFooterV1 flags must be zero",
834 ));
835 }
836 let format_version = read_u16(bytes, 70)?;
837 if format_version != FORMAT_VERSION {
838 return Err(FormatError::UnsupportedFormatVersion(format_version));
839 }
840 let volume_format_rev = read_u16(bytes, 72)?;
841 if volume_format_rev != VOLUME_FORMAT_REV {
842 return Err(FormatError::UnsupportedVolumeFormatRevision(
843 volume_format_rev,
844 ));
845 }
846 let signer_identity_length = read_u32(bytes, 78)?;
847 let authenticator_value_length = read_u32(bytes, 82)?;
848 validate_root_auth_variable_lengths(
849 signer_identity_length as usize,
850 authenticator_value_length as usize,
851 )?;
852 let expected = root_auth_footer_length(
853 signer_identity_length as usize,
854 authenticator_value_length as usize,
855 )?;
856 if expected != footer_length {
857 return Err(FormatError::InvalidLength {
858 structure: "RootAuthFooterV1",
859 expected: expected as usize,
860 actual: footer_length as usize,
861 });
862 }
863 expect_zero("RootAuthFooterV1", &bytes[286..318])?;
864 let crc_offset = bytes.len() - 4;
865 expect_crc(
866 "RootAuthFooterV1",
867 &bytes[..crc_offset],
868 read_u32(bytes, crc_offset)?,
869 )?;
870
871 let signer_start = ROOT_AUTH_FOOTER_FIXED_LEN;
872 let signer_end = signer_start + signer_identity_length as usize;
873 let auth_end = signer_end + authenticator_value_length as usize;
874 Ok(Self {
875 archive_uuid: read_array_16(bytes, 38)?,
876 session_id: read_array_16(bytes, 54)?,
877 authenticator_id: read_u16(bytes, 74)?,
878 signer_identity_type: read_u16(bytes, 76)?,
879 signer_identity_bytes: bytes[signer_start..signer_end].to_vec(),
880 authenticator_value: bytes[signer_end..auth_end].to_vec(),
881 total_data_block_count: read_u64(bytes, 86)?,
882 critical_metadata_digest: read_array_32(bytes, 94)?,
883 index_digest: read_array_32(bytes, 126)?,
884 fec_layout_digest: read_array_32(bytes, 158)?,
885 data_block_merkle_root: read_array_32(bytes, 190)?,
886 signer_identity_digest: read_array_32(bytes, 222)?,
887 archive_root: read_array_32(bytes, 254)?,
888 footer_crc32c: read_u32(bytes, crc_offset)?,
889 })
890 }
891}
892
893fn root_auth_footer_length(
894 signer_identity_len: usize,
895 authenticator_value_len: usize,
896) -> Result<u32, FormatError> {
897 let len = ROOT_AUTH_FOOTER_FIXED_LEN
898 .checked_add(signer_identity_len)
899 .and_then(|value| value.checked_add(authenticator_value_len))
900 .and_then(|value| value.checked_add(4))
901 .ok_or(FormatError::InvalidArchive(
902 "RootAuthFooterV1 length overflow",
903 ))?;
904 if len > READER_MAX_ROOT_AUTH_FOOTER_LEN as usize {
905 return Err(FormatError::ReaderResourceLimitExceeded {
906 field: "RootAuthFooterV1 length",
907 cap: READER_MAX_ROOT_AUTH_FOOTER_LEN as u64,
908 actual: len as u64,
909 });
910 }
911 u32::try_from(len).map_err(|_| FormatError::InvalidArchive("RootAuthFooterV1 length overflow"))
912}
913
914fn validate_root_auth_variable_lengths(
915 signer_identity_len: usize,
916 authenticator_value_len: usize,
917) -> Result<(), FormatError> {
918 if signer_identity_len > READER_MAX_ROOT_AUTH_SIGNER_IDENTITY_LEN as usize {
919 return Err(FormatError::ReaderResourceLimitExceeded {
920 field: "RootAuthFooterV1 signer identity length",
921 cap: READER_MAX_ROOT_AUTH_SIGNER_IDENTITY_LEN as u64,
922 actual: signer_identity_len as u64,
923 });
924 }
925 if authenticator_value_len > READER_MAX_ROOT_AUTH_AUTHENTICATOR_VALUE_LEN as usize {
926 return Err(FormatError::ReaderResourceLimitExceeded {
927 field: "RootAuthFooterV1 authenticator value length",
928 cap: READER_MAX_ROOT_AUTH_AUTHENTICATOR_VALUE_LEN as u64,
929 actual: authenticator_value_len as u64,
930 });
931 }
932 Ok(())
933}
934
935#[derive(Debug, Clone, PartialEq, Eq)]
936pub struct SerializedRegion {
937 pub region_type: u16,
938 pub offset: u64,
939 pub bytes: Vec<u8>,
940}
941
942impl SerializedRegion {
943 pub fn encoded_len(&self) -> usize {
944 SERIALIZED_REGION_HEADER_LEN + self.bytes.len()
945 }
946
947 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
948 let length = u32::try_from(self.bytes.len())
949 .map_err(|_| FormatError::InvalidArchive("SerializedRegion length exceeds u32"))?;
950 let mut bytes = vec![0u8; self.encoded_len()];
951 write_u16(&mut bytes, 0, self.region_type);
952 write_u64(&mut bytes, 4, self.offset);
953 write_u32(&mut bytes, 12, length);
954 bytes[SERIALIZED_REGION_HEADER_LEN..].copy_from_slice(&self.bytes);
955 Ok(bytes)
956 }
957}
958
959#[derive(Debug, Clone, PartialEq, Eq)]
960pub struct CriticalMetadataImage {
961 pub archive_uuid: [u8; 16],
962 pub session_id: [u8; 16],
963 pub volume_index: u32,
964 pub stripe_width: u32,
965 pub layout_flags: u32,
966 pub volume_header_offset: u64,
967 pub volume_header_length: u32,
968 pub crypto_header_offset: u64,
969 pub crypto_header_length: u32,
970 pub block_records_offset: u64,
971 pub block_records_length: u64,
972 pub block_count: u64,
973 pub manifest_footer_offset: u64,
974 pub manifest_footer_length: u32,
975 pub root_auth_footer_offset: u64,
976 pub root_auth_footer_length: u32,
977 pub volume_trailer_offset: u64,
978 pub volume_trailer_length: u32,
979 pub body_bytes_before_cmra: u64,
980 pub volume_header_sha256: [u8; 32],
981 pub crypto_header_sha256: [u8; 32],
982 pub manifest_footer_sha256: [u8; 32],
983 pub root_auth_footer_sha256: [u8; 32],
984 pub volume_trailer_sha256: [u8; 32],
985 pub regions: Vec<SerializedRegion>,
986}
987
988impl CriticalMetadataImage {
989 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
990 let region_count = u16::try_from(self.regions.len()).map_err(|_| {
991 FormatError::InvalidArchive("CriticalMetadataImage has too many regions")
992 })?;
993 let variable_len = self.regions.iter().try_fold(0usize, |total, region| {
994 total
995 .checked_add(region.encoded_len())
996 .ok_or(FormatError::InvalidArchive(
997 "CriticalMetadataImage length overflow",
998 ))
999 })?;
1000 let mut bytes = vec![
1001 0u8;
1002 CRITICAL_METADATA_IMAGE_FIXED_LEN
1003 .checked_add(variable_len)
1004 .and_then(|value| value.checked_add(IMAGE_CRC_LEN))
1005 .ok_or(FormatError::InvalidArchive(
1006 "CriticalMetadataImage length overflow",
1007 ))?
1008 ];
1009 bytes[0..4].copy_from_slice(&TZMI_MAGIC);
1010 write_u16(&mut bytes, 4, 1);
1011 write_u16(&mut bytes, 6, VOLUME_FORMAT_REV);
1012 bytes[8..24].copy_from_slice(&self.archive_uuid);
1013 bytes[24..40].copy_from_slice(&self.session_id);
1014 write_u32(&mut bytes, 40, self.volume_index);
1015 write_u32(&mut bytes, 44, self.stripe_width);
1016 write_u32(&mut bytes, 48, self.layout_flags);
1017 write_u64(&mut bytes, 52, self.volume_header_offset);
1018 write_u32(&mut bytes, 60, self.volume_header_length);
1019 write_u64(&mut bytes, 64, self.crypto_header_offset);
1020 write_u32(&mut bytes, 72, self.crypto_header_length);
1021 write_u64(&mut bytes, 76, self.block_records_offset);
1022 write_u64(&mut bytes, 84, self.block_records_length);
1023 write_u64(&mut bytes, 92, self.block_count);
1024 write_u64(&mut bytes, 100, self.manifest_footer_offset);
1025 write_u32(&mut bytes, 108, self.manifest_footer_length);
1026 write_u64(&mut bytes, 112, self.root_auth_footer_offset);
1027 write_u32(&mut bytes, 120, self.root_auth_footer_length);
1028 write_u64(&mut bytes, 124, self.volume_trailer_offset);
1029 write_u32(&mut bytes, 132, self.volume_trailer_length);
1030 write_u64(&mut bytes, 136, self.body_bytes_before_cmra);
1031 bytes[144..176].copy_from_slice(&self.volume_header_sha256);
1032 bytes[176..208].copy_from_slice(&self.crypto_header_sha256);
1033 bytes[208..240].copy_from_slice(&self.manifest_footer_sha256);
1034 bytes[240..272].copy_from_slice(&self.root_auth_footer_sha256);
1035 bytes[272..304].copy_from_slice(&self.volume_trailer_sha256);
1036 write_u16(&mut bytes, 304, region_count);
1037
1038 let mut cursor = CRITICAL_METADATA_IMAGE_FIXED_LEN;
1039 for region in &self.regions {
1040 let region_bytes = region.to_bytes()?;
1041 let end = cursor + region_bytes.len();
1042 bytes[cursor..end].copy_from_slice(®ion_bytes);
1043 cursor = end;
1044 }
1045 let crc = crc32c(&bytes[..cursor]);
1046 write_u32(&mut bytes, cursor, crc);
1047 Ok(bytes)
1048 }
1049
1050 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1051 if bytes.len() < CRITICAL_METADATA_IMAGE_FIXED_LEN + IMAGE_CRC_LEN {
1052 return Err(FormatError::InvalidLength {
1053 structure: "CriticalMetadataImageV1",
1054 expected: CRITICAL_METADATA_IMAGE_FIXED_LEN + IMAGE_CRC_LEN,
1055 actual: bytes.len(),
1056 });
1057 }
1058 expect_magic("CriticalMetadataImageV1", TZMI_MAGIC, &bytes[0..4])?;
1059 let version = read_u16(bytes, 4)?;
1060 if version != 1 {
1061 return Err(FormatError::UnsupportedFormatVersion(version));
1062 }
1063 let volume_format_rev = read_u16(bytes, 6)?;
1064 if volume_format_rev != VOLUME_FORMAT_REV {
1065 return Err(FormatError::UnsupportedVolumeFormatRevision(
1066 volume_format_rev,
1067 ));
1068 }
1069 let layout_flags = read_u32(bytes, 48)?;
1070 if layout_flags & !0x0000_0001 != 0 {
1071 return Err(FormatError::InvalidArchive(
1072 "CriticalMetadataImage layout_flags has unknown bits",
1073 ));
1074 }
1075 expect_zero("CriticalMetadataImageV1", &bytes[306..320])?;
1076 let expected_crc_offset =
1077 bytes
1078 .len()
1079 .checked_sub(IMAGE_CRC_LEN)
1080 .ok_or(FormatError::InvalidArchive(
1081 "CriticalMetadataImage length underflow",
1082 ))?;
1083 expect_crc(
1084 "CriticalMetadataImageV1",
1085 &bytes[..expected_crc_offset],
1086 read_u32(bytes, expected_crc_offset)?,
1087 )?;
1088
1089 let serialized_region_count = read_u16(bytes, 304)? as usize;
1090 let mut cursor = CRITICAL_METADATA_IMAGE_FIXED_LEN;
1091 let mut regions = Vec::with_capacity(serialized_region_count);
1092 for _ in 0..serialized_region_count {
1093 if cursor + SERIALIZED_REGION_HEADER_LEN > expected_crc_offset {
1094 return Err(FormatError::InvalidLength {
1095 structure: "SerializedRegion",
1096 expected: cursor + SERIALIZED_REGION_HEADER_LEN,
1097 actual: bytes.len(),
1098 });
1099 }
1100 let region_type = read_u16(bytes, cursor)?;
1101 if read_u16(bytes, cursor + 2)? != 0 {
1102 return Err(FormatError::NonZeroReserved {
1103 structure: "SerializedRegion",
1104 });
1105 }
1106 let offset = read_u64(bytes, cursor + 4)?;
1107 let length = read_u32(bytes, cursor + 12)? as usize;
1108 cursor += SERIALIZED_REGION_HEADER_LEN;
1109 let end = cursor
1110 .checked_add(length)
1111 .ok_or(FormatError::InvalidArchive(
1112 "SerializedRegion length overflow",
1113 ))?;
1114 if end > expected_crc_offset {
1115 return Err(FormatError::InvalidLength {
1116 structure: "SerializedRegion",
1117 expected: end,
1118 actual: bytes.len(),
1119 });
1120 }
1121 regions.push(SerializedRegion {
1122 region_type,
1123 offset,
1124 bytes: bytes[cursor..end].to_vec(),
1125 });
1126 cursor = end;
1127 }
1128 if cursor != expected_crc_offset {
1129 return Err(FormatError::InvalidArchive(
1130 "CriticalMetadataImage has trailing region bytes",
1131 ));
1132 }
1133
1134 Ok(Self {
1135 archive_uuid: read_array_16(bytes, 8)?,
1136 session_id: read_array_16(bytes, 24)?,
1137 volume_index: read_u32(bytes, 40)?,
1138 stripe_width: read_u32(bytes, 44)?,
1139 layout_flags,
1140 volume_header_offset: read_u64(bytes, 52)?,
1141 volume_header_length: read_u32(bytes, 60)?,
1142 crypto_header_offset: read_u64(bytes, 64)?,
1143 crypto_header_length: read_u32(bytes, 72)?,
1144 block_records_offset: read_u64(bytes, 76)?,
1145 block_records_length: read_u64(bytes, 84)?,
1146 block_count: read_u64(bytes, 92)?,
1147 manifest_footer_offset: read_u64(bytes, 100)?,
1148 manifest_footer_length: read_u32(bytes, 108)?,
1149 root_auth_footer_offset: read_u64(bytes, 112)?,
1150 root_auth_footer_length: read_u32(bytes, 120)?,
1151 volume_trailer_offset: read_u64(bytes, 124)?,
1152 volume_trailer_length: read_u32(bytes, 132)?,
1153 body_bytes_before_cmra: read_u64(bytes, 136)?,
1154 volume_header_sha256: read_array_32(bytes, 144)?,
1155 crypto_header_sha256: read_array_32(bytes, 176)?,
1156 manifest_footer_sha256: read_array_32(bytes, 208)?,
1157 root_auth_footer_sha256: read_array_32(bytes, 240)?,
1158 volume_trailer_sha256: read_array_32(bytes, 272)?,
1159 regions,
1160 })
1161 }
1162
1163 pub fn region(&self, region_type: u16) -> Option<&SerializedRegion> {
1164 self.regions
1165 .iter()
1166 .find(|region| region.region_type == region_type)
1167 }
1168}
1169
1170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1171pub struct CriticalMetadataRecoveryHeader {
1172 pub shard_size: u32,
1173 pub data_shard_count: u16,
1174 pub parity_shard_count: u16,
1175 pub image_length: u32,
1176 pub archive_uuid_hint: [u8; 16],
1177 pub session_id_hint: [u8; 16],
1178 pub volume_index_hint: u32,
1179 pub image_sha256: [u8; 32],
1180 pub header_crc32c: u32,
1181}
1182
1183impl CriticalMetadataRecoveryHeader {
1184 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1185 expect_len(
1186 "CriticalMetadataRecoveryHeader",
1187 CRITICAL_METADATA_RECOVERY_HEADER_LEN,
1188 bytes.len(),
1189 )?;
1190 expect_magic("CriticalMetadataRecoveryHeader", TZCR_MAGIC, &bytes[0..4])?;
1191 let version = read_u16(bytes, 4)?;
1192 if version != 1 {
1193 return Err(FormatError::UnsupportedFormatVersion(version));
1194 }
1195 let fec_algo = read_u16(bytes, 6)?;
1196 if fec_algo != FecAlgo::ReedSolomonGF16 as u16 {
1197 return Err(FormatError::UnknownFecAlgo(fec_algo));
1198 }
1199 expect_zero("CriticalMetadataRecoveryHeader", &bytes[88..112])?;
1200 expect_crc(
1201 "CriticalMetadataRecoveryHeader",
1202 &bytes[..112],
1203 read_u32(bytes, 112)?,
1204 )?;
1205 Ok(Self {
1206 shard_size: read_u32(bytes, 8)?,
1207 data_shard_count: read_u16(bytes, 12)?,
1208 parity_shard_count: read_u16(bytes, 14)?,
1209 image_length: read_u32(bytes, 16)?,
1210 archive_uuid_hint: read_array_16(bytes, 20)?,
1211 session_id_hint: read_array_16(bytes, 36)?,
1212 volume_index_hint: read_u32(bytes, 52)?,
1213 image_sha256: read_array_32(bytes, 56)?,
1214 header_crc32c: read_u32(bytes, 112)?,
1215 })
1216 }
1217
1218 pub fn to_bytes(&self) -> [u8; CRITICAL_METADATA_RECOVERY_HEADER_LEN] {
1219 let mut bytes = [0u8; CRITICAL_METADATA_RECOVERY_HEADER_LEN];
1220 bytes[0..4].copy_from_slice(&TZCR_MAGIC);
1221 write_u16(&mut bytes, 4, 1);
1222 write_u16(&mut bytes, 6, FecAlgo::ReedSolomonGF16 as u16);
1223 write_u32(&mut bytes, 8, self.shard_size);
1224 write_u16(&mut bytes, 12, self.data_shard_count);
1225 write_u16(&mut bytes, 14, self.parity_shard_count);
1226 write_u32(&mut bytes, 16, self.image_length);
1227 bytes[20..36].copy_from_slice(&self.archive_uuid_hint);
1228 bytes[36..52].copy_from_slice(&self.session_id_hint);
1229 write_u32(&mut bytes, 52, self.volume_index_hint);
1230 bytes[56..88].copy_from_slice(&self.image_sha256);
1231 let crc = crc32c(&bytes[..112]);
1232 write_u32(&mut bytes, 112, crc);
1233 bytes
1234 }
1235}
1236
1237#[derive(Debug, Clone, PartialEq, Eq)]
1238pub struct CriticalMetadataRecoveryShard {
1239 pub shard_index: u16,
1240 pub shard_role: u8,
1241 pub shard_payload_length: u32,
1242 pub payload: Vec<u8>,
1243 pub shard_crc32c: u32,
1244}
1245
1246impl CriticalMetadataRecoveryShard {
1247 pub fn parse(bytes: &[u8], shard_size: usize) -> Result<Self, FormatError> {
1248 let expected = CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN
1249 .checked_add(shard_size)
1250 .ok_or(FormatError::InvalidArchive("CMRA shard length overflow"))?;
1251 expect_len("CriticalMetadataRecoveryShard", expected, bytes.len())?;
1252 expect_magic("CriticalMetadataRecoveryShard", TZCS_MAGIC, &bytes[0..4])?;
1253 if bytes[7] != 0 {
1254 return Err(FormatError::NonZeroReserved {
1255 structure: "CriticalMetadataRecoveryShard",
1256 });
1257 }
1258 let shard_role = bytes[6];
1259 if shard_role > 1 {
1260 return Err(FormatError::InvalidArchive(
1261 "CriticalMetadataRecoveryShard has unknown role",
1262 ));
1263 }
1264 expect_crc(
1265 "CriticalMetadataRecoveryShard",
1266 &[
1267 &bytes[..12],
1268 &bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..],
1269 ]
1270 .concat(),
1271 read_u32(bytes, 12)?,
1272 )?;
1273 let shard_payload_length = read_u32(bytes, 8)?;
1274 if shard_payload_length as usize > shard_size {
1275 return Err(FormatError::InvalidArchive(
1276 "CriticalMetadataRecoveryShard payload length exceeds shard size",
1277 ));
1278 }
1279 Ok(Self {
1280 shard_index: read_u16(bytes, 4)?,
1281 shard_role,
1282 shard_payload_length,
1283 payload: bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..].to_vec(),
1284 shard_crc32c: read_u32(bytes, 12)?,
1285 })
1286 }
1287
1288 pub fn to_bytes(&self, shard_size: usize) -> Result<Vec<u8>, FormatError> {
1289 if self.payload.len() != shard_size {
1290 return Err(FormatError::InvalidArchive(
1291 "CriticalMetadataRecoveryShard payload length mismatch",
1292 ));
1293 }
1294 let mut bytes = vec![0u8; CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN + shard_size];
1295 bytes[0..4].copy_from_slice(&TZCS_MAGIC);
1296 write_u16(&mut bytes, 4, self.shard_index);
1297 bytes[6] = self.shard_role;
1298 write_u32(&mut bytes, 8, self.shard_payload_length);
1299 bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..].copy_from_slice(&self.payload);
1300 let mut covered = Vec::with_capacity(12 + shard_size);
1301 covered.extend_from_slice(&bytes[..12]);
1302 covered.extend_from_slice(&bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..]);
1303 let crc = crc32c(&covered);
1304 write_u32(&mut bytes, 12, crc);
1305 Ok(bytes)
1306 }
1307}
1308
1309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1310pub struct CriticalRecoveryLocator {
1311 pub cmra_offset: u64,
1312 pub cmra_length: u32,
1313 pub volume_trailer_offset: u64,
1314 pub body_bytes_before_cmra: u64,
1315 pub archive_uuid_hint: [u8; 16],
1316 pub session_id_hint: [u8; 16],
1317 pub volume_index_hint: u32,
1318 pub locator_sequence: u32,
1319 pub cmra_shard_size: u32,
1320 pub cmra_data_shard_count: u16,
1321 pub cmra_parity_shard_count: u16,
1322 pub cmra_image_length: u32,
1323 pub cmra_image_sha256: [u8; 32],
1324 pub locator_crc32c: u32,
1325}
1326
1327impl CriticalRecoveryLocator {
1328 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1329 expect_len(
1330 "CriticalRecoveryLocator",
1331 CRITICAL_RECOVERY_LOCATOR_LEN,
1332 bytes.len(),
1333 )?;
1334 expect_magic("CriticalRecoveryLocator", TZCL_MAGIC, &bytes[0..4])?;
1335 let version = read_u16(bytes, 4)?;
1336 if version != 1 {
1337 return Err(FormatError::UnsupportedFormatVersion(version));
1338 }
1339 let volume_format_rev = read_u16(bytes, 6)?;
1340 if volume_format_rev != VOLUME_FORMAT_REV {
1341 return Err(FormatError::UnsupportedVolumeFormatRevision(
1342 volume_format_rev,
1343 ));
1344 }
1345 if read_u16(bytes, 20)? != CRITICAL_METADATA_RECOVERY_HEADER_LEN as u16 {
1346 return Err(FormatError::InvalidArchive(
1347 "CriticalRecoveryLocator CMRA header length is invalid",
1348 ));
1349 }
1350 let fec_algo = read_u16(bytes, 22)?;
1351 if fec_algo != FecAlgo::ReedSolomonGF16 as u16 {
1352 return Err(FormatError::UnknownFecAlgo(fec_algo));
1353 }
1354 let locator_sequence = read_u32(bytes, 76)?;
1355 if locator_sequence > 1 {
1356 return Err(FormatError::InvalidArchive(
1357 "CriticalRecoveryLocator has invalid sequence",
1358 ));
1359 }
1360 expect_crc(
1361 "CriticalRecoveryLocator",
1362 &bytes[..124],
1363 read_u32(bytes, 124)?,
1364 )?;
1365
1366 Ok(Self {
1367 cmra_offset: read_u64(bytes, 8)?,
1368 cmra_length: read_u32(bytes, 16)?,
1369 volume_trailer_offset: read_u64(bytes, 24)?,
1370 body_bytes_before_cmra: read_u64(bytes, 32)?,
1371 archive_uuid_hint: read_array_16(bytes, 40)?,
1372 session_id_hint: read_array_16(bytes, 56)?,
1373 volume_index_hint: read_u32(bytes, 72)?,
1374 locator_sequence,
1375 cmra_shard_size: read_u32(bytes, 80)?,
1376 cmra_data_shard_count: read_u16(bytes, 84)?,
1377 cmra_parity_shard_count: read_u16(bytes, 86)?,
1378 cmra_image_length: read_u32(bytes, 88)?,
1379 cmra_image_sha256: read_array_32(bytes, 92)?,
1380 locator_crc32c: read_u32(bytes, 124)?,
1381 })
1382 }
1383
1384 pub fn to_bytes(&self) -> [u8; CRITICAL_RECOVERY_LOCATOR_LEN] {
1385 let mut bytes = [0u8; CRITICAL_RECOVERY_LOCATOR_LEN];
1386 bytes[0..4].copy_from_slice(&TZCL_MAGIC);
1387 write_u16(&mut bytes, 4, 1);
1388 write_u16(&mut bytes, 6, VOLUME_FORMAT_REV);
1389 write_u64(&mut bytes, 8, self.cmra_offset);
1390 write_u32(&mut bytes, 16, self.cmra_length);
1391 write_u16(&mut bytes, 20, CRITICAL_METADATA_RECOVERY_HEADER_LEN as u16);
1392 write_u16(&mut bytes, 22, FecAlgo::ReedSolomonGF16 as u16);
1393 write_u64(&mut bytes, 24, self.volume_trailer_offset);
1394 write_u64(&mut bytes, 32, self.body_bytes_before_cmra);
1395 bytes[40..56].copy_from_slice(&self.archive_uuid_hint);
1396 bytes[56..72].copy_from_slice(&self.session_id_hint);
1397 write_u32(&mut bytes, 72, self.volume_index_hint);
1398 write_u32(&mut bytes, 76, self.locator_sequence);
1399 write_u32(&mut bytes, 80, self.cmra_shard_size);
1400 write_u16(&mut bytes, 84, self.cmra_data_shard_count);
1401 write_u16(&mut bytes, 86, self.cmra_parity_shard_count);
1402 write_u32(&mut bytes, 88, self.cmra_image_length);
1403 bytes[92..124].copy_from_slice(&self.cmra_image_sha256);
1404 let crc = crc32c(&bytes[..124]);
1405 write_u32(&mut bytes, 124, crc);
1406 bytes
1407 }
1408}
1409
1410#[derive(Debug, Clone, PartialEq, Eq)]
1411pub struct BootstrapSidecarHeader {
1412 pub archive_uuid: [u8; 16],
1413 pub session_id: [u8; 16],
1414 pub flags: u32,
1415 pub manifest_footer_offset: u64,
1416 pub manifest_footer_length: u32,
1417 pub index_root_records_offset: u64,
1418 pub index_root_records_length: u64,
1419 pub dictionary_records_offset: u64,
1420 pub dictionary_records_length: u64,
1421 pub sidecar_hmac: [u8; 32],
1422 pub header_crc32c: u32,
1423}
1424
1425impl BootstrapSidecarHeader {
1426 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1427 expect_len(
1428 "BootstrapSidecarHeader",
1429 BOOTSTRAP_SIDECAR_HEADER_LEN,
1430 bytes.len(),
1431 )?;
1432 expect_magic("BootstrapSidecarHeader", TZBS_MAGIC, &bytes[0..4])?;
1433 let version = read_u32(bytes, 4)?;
1434 if version != 1 {
1435 return Err(FormatError::UnsupportedBootstrapSidecarVersion(version));
1436 }
1437 expect_zero("BootstrapSidecarHeader", &bytes[88..92])?;
1438 expect_crc(
1439 "BootstrapSidecarHeader",
1440 &bytes[..124],
1441 read_u32(bytes, 124)?,
1442 )?;
1443
1444 let header = Self {
1445 archive_uuid: read_array_16(bytes, 8)?,
1446 session_id: read_array_16(bytes, 24)?,
1447 flags: read_u32(bytes, 40)?,
1448 manifest_footer_offset: read_u64(bytes, 44)?,
1449 manifest_footer_length: read_u32(bytes, 52)?,
1450 index_root_records_offset: read_u64(bytes, 56)?,
1451 index_root_records_length: read_u64(bytes, 64)?,
1452 dictionary_records_offset: read_u64(bytes, 72)?,
1453 dictionary_records_length: read_u64(bytes, 80)?,
1454 sidecar_hmac: read_array_32(bytes, 92)?,
1455 header_crc32c: read_u32(bytes, 124)?,
1456 };
1457 header.validate_sections()?;
1458 Ok(header)
1459 }
1460
1461 pub fn validate_packed_layout(&self, file_size: u64) -> Result<(), FormatError> {
1462 let mut cursor = BOOTSTRAP_SIDECAR_HEADER_LEN as u64;
1463 cursor = self.validate_section_cursor(
1464 self.has_manifest_footer(),
1465 self.manifest_footer_offset,
1466 self.manifest_footer_length as u64,
1467 cursor,
1468 )?;
1469 cursor = self.validate_section_cursor(
1470 self.has_index_root_records(),
1471 self.index_root_records_offset,
1472 self.index_root_records_length,
1473 cursor,
1474 )?;
1475 cursor = self.validate_section_cursor(
1476 self.has_dictionary_records(),
1477 self.dictionary_records_offset,
1478 self.dictionary_records_length,
1479 cursor,
1480 )?;
1481 if cursor != file_size {
1482 return Err(FormatError::NonCanonicalBootstrapSidecarLayout);
1483 }
1484 Ok(())
1485 }
1486
1487 pub fn has_manifest_footer(&self) -> bool {
1488 self.flags & SIDECAR_MANIFEST_PRESENT != 0
1489 }
1490
1491 pub fn has_index_root_records(&self) -> bool {
1492 self.flags & SIDECAR_INDEX_ROOT_PRESENT != 0
1493 }
1494
1495 pub fn has_dictionary_records(&self) -> bool {
1496 self.flags & SIDECAR_DICTIONARY_PRESENT != 0
1497 }
1498
1499 pub fn to_bytes(&self) -> [u8; BOOTSTRAP_SIDECAR_HEADER_LEN] {
1500 let mut bytes = [0u8; BOOTSTRAP_SIDECAR_HEADER_LEN];
1501 bytes[0..4].copy_from_slice(&TZBS_MAGIC);
1502 write_u32(&mut bytes, 4, 1);
1503 bytes[8..24].copy_from_slice(&self.archive_uuid);
1504 bytes[24..40].copy_from_slice(&self.session_id);
1505 write_u32(&mut bytes, 40, self.flags);
1506 write_u64(&mut bytes, 44, self.manifest_footer_offset);
1507 write_u32(&mut bytes, 52, self.manifest_footer_length);
1508 write_u64(&mut bytes, 56, self.index_root_records_offset);
1509 write_u64(&mut bytes, 64, self.index_root_records_length);
1510 write_u64(&mut bytes, 72, self.dictionary_records_offset);
1511 write_u64(&mut bytes, 80, self.dictionary_records_length);
1512 bytes[92..124].copy_from_slice(&self.sidecar_hmac);
1513 let crc = crc32c(&bytes[..124]);
1514 write_u32(&mut bytes, 124, crc);
1515 bytes
1516 }
1517
1518 fn validate_sections(&self) -> Result<(), FormatError> {
1519 if self.flags & !SIDECAR_KNOWN_FLAGS != 0 {
1520 return Err(FormatError::UnknownBootstrapSidecarFlags(self.flags));
1521 }
1522 self.validate_presence_fields(
1523 self.has_manifest_footer(),
1524 self.manifest_footer_offset,
1525 self.manifest_footer_length as u64,
1526 )?;
1527 if self.has_manifest_footer() && self.manifest_footer_length != MANIFEST_FOOTER_LEN as u32 {
1528 return Err(FormatError::InvalidManifestFooterLength(
1529 self.manifest_footer_length,
1530 ));
1531 }
1532 self.validate_presence_fields(
1533 self.has_index_root_records(),
1534 self.index_root_records_offset,
1535 self.index_root_records_length,
1536 )?;
1537 self.validate_presence_fields(
1538 self.has_dictionary_records(),
1539 self.dictionary_records_offset,
1540 self.dictionary_records_length,
1541 )?;
1542 Ok(())
1543 }
1544
1545 fn validate_presence_fields(
1546 &self,
1547 present: bool,
1548 offset: u64,
1549 length: u64,
1550 ) -> Result<(), FormatError> {
1551 match (present, offset, length) {
1552 (true, 0, _) | (true, _, 0) => Err(FormatError::EmptyBootstrapSidecarSection),
1553 (false, 0, 0) => Ok(()),
1554 (false, _, _) => Err(FormatError::NonZeroAbsentBootstrapSidecarSection),
1555 (true, _, _) => Ok(()),
1556 }
1557 }
1558
1559 fn validate_section_cursor(
1560 &self,
1561 present: bool,
1562 offset: u64,
1563 length: u64,
1564 cursor: u64,
1565 ) -> Result<u64, FormatError> {
1566 if present {
1567 if offset != cursor {
1568 return Err(FormatError::NonCanonicalBootstrapSidecarLayout);
1569 }
1570 cursor
1571 .checked_add(length)
1572 .ok_or(FormatError::NonCanonicalBootstrapSidecarLayout)
1573 } else {
1574 Ok(cursor)
1575 }
1576 }
1577}
1578
1579fn is_known_extension(ext_tag: u16) -> bool {
1580 matches!(ext_tag, 0x0001 | 0x0002 | 0x0003 | 0x0005)
1581}
1582
1583fn validate_known_extension(ext_tag: u16, value: &[u8]) -> Result<(), FormatError> {
1584 match ext_tag {
1585 0x0001 | 0x0002 | 0x0005 => std::str::from_utf8(value)
1586 .map(|_| ())
1587 .map_err(|_| FormatError::MalformedKnownExtension(ext_tag)),
1588 0x0003 => {
1589 if value.len() == 8 {
1590 Ok(())
1591 } else {
1592 Err(FormatError::MalformedKnownExtension(ext_tag))
1593 }
1594 }
1595 _ => Ok(()),
1596 }
1597}
1598
1599fn expect_len(structure: &'static str, expected: usize, actual: usize) -> Result<(), FormatError> {
1600 if actual != expected {
1601 return Err(FormatError::InvalidLength {
1602 structure,
1603 expected,
1604 actual,
1605 });
1606 }
1607 Ok(())
1608}
1609
1610fn expect_magic(
1611 structure: &'static str,
1612 expected: [u8; 4],
1613 actual: &[u8],
1614) -> Result<(), FormatError> {
1615 if actual != expected {
1616 return Err(FormatError::BadMagic { structure });
1617 }
1618 Ok(())
1619}
1620
1621fn expect_zero(structure: &'static str, bytes: &[u8]) -> Result<(), FormatError> {
1622 if bytes.iter().any(|byte| *byte != 0) {
1623 return Err(FormatError::NonZeroReserved { structure });
1624 }
1625 Ok(())
1626}
1627
1628fn expect_crc(structure: &'static str, covered: &[u8], expected: u32) -> Result<(), FormatError> {
1629 if crc32c(covered) != expected {
1630 return Err(FormatError::BadCrc { structure });
1631 }
1632 Ok(())
1633}
1634
1635fn read_array_16(bytes: &[u8], offset: usize) -> Result<[u8; 16], FormatError> {
1636 let mut value = [0u8; 16];
1637 value.copy_from_slice(
1638 bytes
1639 .get(offset..offset + 16)
1640 .ok_or(FormatError::InvalidLength {
1641 structure: "array16",
1642 expected: offset + 16,
1643 actual: bytes.len(),
1644 })?,
1645 );
1646 Ok(value)
1647}
1648
1649fn read_array_24(bytes: &[u8], offset: usize) -> Result<[u8; 24], FormatError> {
1650 let mut value = [0u8; 24];
1651 value.copy_from_slice(
1652 bytes
1653 .get(offset..offset + 24)
1654 .ok_or(FormatError::InvalidLength {
1655 structure: "array24",
1656 expected: offset + 24,
1657 actual: bytes.len(),
1658 })?,
1659 );
1660 Ok(value)
1661}
1662
1663fn read_array_32(bytes: &[u8], offset: usize) -> Result<[u8; 32], FormatError> {
1664 let mut value = [0u8; 32];
1665 value.copy_from_slice(
1666 bytes
1667 .get(offset..offset + 32)
1668 .ok_or(FormatError::InvalidLength {
1669 structure: "array32",
1670 expected: offset + 32,
1671 actual: bytes.len(),
1672 })?,
1673 );
1674 Ok(value)
1675}
1676
1677fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, FormatError> {
1678 let array: [u8; 2] = bytes
1679 .get(offset..offset + 2)
1680 .ok_or(FormatError::InvalidLength {
1681 structure: "u16",
1682 expected: offset + 2,
1683 actual: bytes.len(),
1684 })?
1685 .try_into()
1686 .expect("slice length checked");
1687 Ok(u16::from_le_bytes(array))
1688}
1689
1690fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, FormatError> {
1691 let array: [u8; 4] = bytes
1692 .get(offset..offset + 4)
1693 .ok_or(FormatError::InvalidLength {
1694 structure: "u32",
1695 expected: offset + 4,
1696 actual: bytes.len(),
1697 })?
1698 .try_into()
1699 .expect("slice length checked");
1700 Ok(u32::from_le_bytes(array))
1701}
1702
1703fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, FormatError> {
1704 let array: [u8; 8] = bytes
1705 .get(offset..offset + 8)
1706 .ok_or(FormatError::InvalidLength {
1707 structure: "u64",
1708 expected: offset + 8,
1709 actual: bytes.len(),
1710 })?
1711 .try_into()
1712 .expect("slice length checked");
1713 Ok(u64::from_le_bytes(array))
1714}
1715
1716fn read_i64(bytes: &[u8], offset: usize) -> Result<i64, FormatError> {
1717 let array: [u8; 8] = bytes
1718 .get(offset..offset + 8)
1719 .ok_or(FormatError::InvalidLength {
1720 structure: "i64",
1721 expected: offset + 8,
1722 actual: bytes.len(),
1723 })?
1724 .try_into()
1725 .expect("slice length checked");
1726 Ok(i64::from_le_bytes(array))
1727}
1728
1729fn write_u16(bytes: &mut [u8], offset: usize, value: u16) {
1730 bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
1731}
1732
1733fn write_u32(bytes: &mut [u8], offset: usize, value: u32) {
1734 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
1735}
1736
1737fn write_u64(bytes: &mut [u8], offset: usize, value: u64) {
1738 bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
1739}
1740
1741fn write_i64(bytes: &mut [u8], offset: usize, value: i64) {
1742 bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
1743}
1744
1745#[cfg(test)]
1746mod tests {
1747 use super::*;
1748 use crate::format::CRYPTO_HEADER_HMAC_LEN;
1749
1750 fn uuid() -> [u8; 16] {
1751 [0x11; 16]
1752 }
1753
1754 fn session() -> [u8; 16] {
1755 [0x22; 16]
1756 }
1757
1758 fn volume_header() -> VolumeHeader {
1759 VolumeHeader {
1760 format_version: FORMAT_VERSION,
1761 volume_format_rev: VOLUME_FORMAT_REV,
1762 volume_index: 0,
1763 stripe_width: 1,
1764 archive_uuid: uuid(),
1765 session_id: session(),
1766 crypto_header_offset: VOLUME_HEADER_LEN as u32,
1767 crypto_header_length: 128,
1768 header_crc32c: 0,
1769 }
1770 }
1771
1772 fn crypto_fixed() -> CryptoHeaderFixed {
1773 CryptoHeaderFixed {
1774 length: (CRYPTO_HEADER_FIXED_LEN + 2 + 6 + CRYPTO_HEADER_HMAC_LEN) as u32,
1775 compression_algo: CompressionAlgo::ZstdFramed,
1776 aead_algo: AeadAlgo::AesGcmSiv256,
1777 fec_algo: FecAlgo::ReedSolomonGF16,
1778 kdf_algo: KdfAlgo::Raw,
1779 chunk_size: 262_144,
1780 envelope_target_size: 4 * 1024 * 1024,
1781 block_size: 4096,
1782 fec_data_shards: 16,
1783 fec_parity_shards: 2,
1784 index_fec_data_shards: 16,
1785 index_fec_parity_shards: 2,
1786 index_root_fec_data_shards: 16,
1787 index_root_fec_parity_shards: 2,
1788 stripe_width: 1,
1789 volume_loss_tolerance: 0,
1790 bit_rot_buffer_pct: 5,
1791 has_dictionary: 0,
1792 max_path_length: 4096,
1793 expected_volume_size: 0,
1794 }
1795 }
1796
1797 fn raw_crypto_header_bytes() -> Vec<u8> {
1798 let fixed = crypto_fixed();
1799 let mut bytes = Vec::new();
1800 bytes.extend_from_slice(&fixed.to_bytes());
1801 bytes.extend_from_slice(&(KdfAlgo::Raw as u16).to_le_bytes());
1802 bytes.extend_from_slice(&0u16.to_le_bytes());
1803 bytes.extend_from_slice(&0u32.to_le_bytes());
1804 bytes.extend_from_slice(&[0xab; CRYPTO_HEADER_HMAC_LEN]);
1805 bytes
1806 }
1807
1808 #[test]
1809 fn volume_header_round_trips_and_validates() {
1810 let bytes = volume_header().to_bytes();
1811 let parsed = VolumeHeader::parse(&bytes).unwrap();
1812 assert_eq!(parsed.format_version, FORMAT_VERSION);
1813 assert_eq!(parsed.volume_format_rev, VOLUME_FORMAT_REV);
1814 assert_eq!(parsed.crypto_header_offset, VOLUME_HEADER_LEN as u32);
1815 }
1816
1817 #[test]
1818 fn volume_header_rejects_mutations() {
1819 let mut bytes = volume_header().to_bytes();
1820 bytes[0] = b'X';
1821 assert_eq!(
1822 VolumeHeader::parse(&bytes).unwrap_err(),
1823 FormatError::BadMagic {
1824 structure: "VolumeHeader"
1825 }
1826 );
1827
1828 let mut bytes = volume_header().to_bytes();
1829 write_u16(&mut bytes, 6, 35);
1830 let crc = crc32c(&bytes[..124]);
1831 write_u32(&mut bytes, 124, crc);
1832 assert_eq!(
1833 VolumeHeader::parse(&bytes).unwrap_err(),
1834 FormatError::UnsupportedVolumeFormatRevision(35)
1835 );
1836
1837 let mut bytes = volume_header().to_bytes();
1838 bytes[124] ^= 1;
1839 assert_eq!(
1840 VolumeHeader::parse(&bytes).unwrap_err(),
1841 FormatError::BadCrc {
1842 structure: "VolumeHeader"
1843 }
1844 );
1845
1846 let mut bytes = volume_header().to_bytes();
1847 write_u32(&mut bytes, 48, 129);
1848 let crc = crc32c(&bytes[..124]);
1849 write_u32(&mut bytes, 124, crc);
1850 assert_eq!(
1851 VolumeHeader::parse(&bytes).unwrap_err(),
1852 FormatError::NonCanonicalCryptoHeaderOffset(129)
1853 );
1854
1855 let mut bytes = volume_header().to_bytes();
1856 write_u32(&mut bytes, 52, READER_MAX_CRYPTO_HEADER_LEN + 1);
1857 let crc = crc32c(&bytes[..124]);
1858 write_u32(&mut bytes, 124, crc);
1859 assert_eq!(
1860 VolumeHeader::parse(&bytes).unwrap_err(),
1861 FormatError::ReaderResourceLimitExceeded {
1862 field: "CryptoHeader length",
1863 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
1864 actual: (READER_MAX_CRYPTO_HEADER_LEN + 1) as u64,
1865 }
1866 );
1867 }
1868
1869 #[test]
1870 fn fixed_structure_magic_matrix_rejects_all_magic_fields() {
1871 let mut volume = volume_header().to_bytes();
1872 volume[0] ^= 0x01;
1873 assert_eq!(
1874 VolumeHeader::parse(&volume).unwrap_err(),
1875 FormatError::BadMagic {
1876 structure: "VolumeHeader"
1877 }
1878 );
1879
1880 let mut crypto = crypto_fixed().to_bytes();
1881 crypto[0] ^= 0x01;
1882 assert_eq!(
1883 CryptoHeaderFixed::parse(&crypto, crypto_fixed().length).unwrap_err(),
1884 FormatError::BadMagic {
1885 structure: "CryptoHeaderFixed"
1886 }
1887 );
1888
1889 let record = BlockRecord {
1890 block_index: 0,
1891 kind: BlockKind::PayloadData,
1892 flags: BLOCK_LAST_DATA_FLAG,
1893 payload: vec![7; 4096],
1894 record_crc32c: 0,
1895 };
1896 let mut block = record.to_bytes();
1897 block[0] ^= 0x01;
1898 assert_eq!(
1899 BlockRecord::parse(&block, 4096).unwrap_err(),
1900 FormatError::BadMagic {
1901 structure: "BlockRecord"
1902 }
1903 );
1904
1905 let footer = ManifestFooter {
1906 archive_uuid: uuid(),
1907 session_id: session(),
1908 volume_index: 0,
1909 is_authoritative: 1,
1910 total_volumes: 1,
1911 index_root_first_block: 0,
1912 index_root_data_block_count: 1,
1913 index_root_parity_block_count: 0,
1914 index_root_encrypted_size: 4096,
1915 index_root_decompressed_size: 120,
1916 manifest_hmac: [0xaa; 32],
1917 };
1918 let mut manifest = footer.to_bytes();
1919 manifest[0] ^= 0x01;
1920 assert_eq!(
1921 ManifestFooter::parse(&manifest).unwrap_err(),
1922 FormatError::BadMagic {
1923 structure: "ManifestFooter"
1924 }
1925 );
1926
1927 let trailer = VolumeTrailer {
1928 archive_uuid: uuid(),
1929 session_id: session(),
1930 volume_index: 0,
1931 block_count: 3,
1932 bytes_written: 10_000,
1933 manifest_footer_offset: 9_864,
1934 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
1935 closed_at_ns: 123,
1936 root_auth_footer_offset: 0,
1937 root_auth_footer_length: 0,
1938 root_auth_flags: 0,
1939 trailer_hmac: [0xbb; 32],
1940 };
1941 let mut trailer_bytes = trailer.to_bytes();
1942 trailer_bytes[0] ^= 0x01;
1943 assert_eq!(
1944 VolumeTrailer::parse(&trailer_bytes).unwrap_err(),
1945 FormatError::BadMagic {
1946 structure: "VolumeTrailer"
1947 }
1948 );
1949
1950 let sidecar = BootstrapSidecarHeader {
1951 archive_uuid: uuid(),
1952 session_id: session(),
1953 flags: SIDECAR_MANIFEST_PRESENT,
1954 manifest_footer_offset: BOOTSTRAP_SIDECAR_HEADER_LEN as u64,
1955 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
1956 index_root_records_offset: 0,
1957 index_root_records_length: 0,
1958 dictionary_records_offset: 0,
1959 dictionary_records_length: 0,
1960 sidecar_hmac: [0xcc; 32],
1961 header_crc32c: 0,
1962 };
1963 let mut sidecar_bytes = sidecar.to_bytes();
1964 sidecar_bytes[0] ^= 0x01;
1965 assert_eq!(
1966 BootstrapSidecarHeader::parse(&sidecar_bytes).unwrap_err(),
1967 FormatError::BadMagic {
1968 structure: "BootstrapSidecarHeader"
1969 }
1970 );
1971 }
1972
1973 #[test]
1974 fn crypto_header_fixed_round_trips_and_validates() {
1975 let header = crypto_fixed();
1976 let bytes = header.to_bytes();
1977 let parsed = CryptoHeaderFixed::parse(&bytes, header.length).unwrap();
1978 assert_eq!(parsed.compression_algo, CompressionAlgo::ZstdFramed);
1979 assert_eq!(parsed.fec_algo, FecAlgo::ReedSolomonGF16);
1980 }
1981
1982 #[test]
1983 fn crypto_header_fixed_rejects_unsupported_profile_values() {
1984 let mut header = crypto_fixed();
1985 header.compression_algo = CompressionAlgo::None;
1986 assert_eq!(
1987 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
1988 FormatError::UnsupportedCompression(CompressionAlgo::None)
1989 );
1990
1991 let mut header = crypto_fixed();
1992 header.block_size = 4097;
1993 assert_eq!(
1994 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
1995 FormatError::OddBlockSize(4097)
1996 );
1997
1998 let mut bytes = crypto_fixed().to_bytes();
1999 bytes[47] = 1;
2000 assert_eq!(
2001 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2002 FormatError::NonZeroReserved {
2003 structure: "CryptoHeaderFixed"
2004 }
2005 );
2006 }
2007
2008 #[test]
2009 fn crypto_header_fixed_rejects_parameter_mutation_matrix() {
2010 let mut bytes = crypto_fixed().to_bytes();
2011 write_u16(&mut bytes, 8, 99);
2012 assert_eq!(
2013 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2014 FormatError::UnknownCompressionAlgo(99)
2015 );
2016
2017 let mut bytes = crypto_fixed().to_bytes();
2018 write_u16(&mut bytes, 10, 99);
2019 assert_eq!(
2020 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2021 FormatError::UnknownAeadAlgo(99)
2022 );
2023
2024 let mut bytes = crypto_fixed().to_bytes();
2025 write_u16(&mut bytes, 12, 99);
2026 assert_eq!(
2027 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2028 FormatError::UnknownFecAlgo(99)
2029 );
2030
2031 let mut bytes = crypto_fixed().to_bytes();
2032 write_u16(&mut bytes, 14, 99);
2033 assert_eq!(
2034 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2035 FormatError::UnknownKdfAlgo(99)
2036 );
2037
2038 let cases: Vec<(&'static str, CryptoHeaderFixed, FormatError)> = vec![
2039 (
2040 "unsupported FEC None",
2041 CryptoHeaderFixed {
2042 fec_algo: FecAlgo::None,
2043 ..crypto_fixed()
2044 },
2045 FormatError::UnsupportedFec(FecAlgo::None),
2046 ),
2047 (
2048 "unsupported FEC Wirehair",
2049 CryptoHeaderFixed {
2050 fec_algo: FecAlgo::Wirehair,
2051 ..crypto_fixed()
2052 },
2053 FormatError::UnsupportedFec(FecAlgo::Wirehair),
2054 ),
2055 (
2056 "invalid dictionary flag",
2057 CryptoHeaderFixed {
2058 has_dictionary: 2,
2059 ..crypto_fixed()
2060 },
2061 FormatError::InvalidBoolean {
2062 field: "has_dictionary",
2063 value: 2,
2064 },
2065 ),
2066 (
2067 "zero stripe width",
2068 CryptoHeaderFixed {
2069 stripe_width: 0,
2070 ..crypto_fixed()
2071 },
2072 FormatError::ZeroStripeWidth,
2073 ),
2074 (
2075 "stripe width cap",
2076 CryptoHeaderFixed {
2077 stripe_width: READER_MAX_STRIPE_WIDTH + 1,
2078 ..crypto_fixed()
2079 },
2080 FormatError::ReaderResourceLimitExceeded {
2081 field: "stripe_width",
2082 cap: READER_MAX_STRIPE_WIDTH as u64,
2083 actual: (READER_MAX_STRIPE_WIDTH + 1) as u64,
2084 },
2085 ),
2086 (
2087 "loss tolerance must be below stripe width",
2088 CryptoHeaderFixed {
2089 stripe_width: 2,
2090 volume_loss_tolerance: 2,
2091 ..crypto_fixed()
2092 },
2093 FormatError::VolumeLossToleranceOutOfRange {
2094 volume_loss_tolerance: 2,
2095 stripe_width: 2,
2096 },
2097 ),
2098 (
2099 "bit rot pct cap",
2100 CryptoHeaderFixed {
2101 bit_rot_buffer_pct: 101,
2102 ..crypto_fixed()
2103 },
2104 FormatError::BitRotBufferPctTooLarge(101),
2105 ),
2106 (
2107 "zero payload data shards",
2108 CryptoHeaderFixed {
2109 fec_data_shards: 0,
2110 ..crypto_fixed()
2111 },
2112 FormatError::ZeroDataShardMaximum {
2113 field: "fec_data_shards",
2114 },
2115 ),
2116 (
2117 "zero index data shards",
2118 CryptoHeaderFixed {
2119 index_fec_data_shards: 0,
2120 ..crypto_fixed()
2121 },
2122 FormatError::ZeroDataShardMaximum {
2123 field: "index_fec_data_shards",
2124 },
2125 ),
2126 (
2127 "zero index root data shards",
2128 CryptoHeaderFixed {
2129 index_root_fec_data_shards: 0,
2130 ..crypto_fixed()
2131 },
2132 FormatError::ZeroDataShardMaximum {
2133 field: "index_root_fec_data_shards",
2134 },
2135 ),
2136 (
2137 "zero chunk size",
2138 CryptoHeaderFixed {
2139 chunk_size: 0,
2140 ..crypto_fixed()
2141 },
2142 FormatError::ZeroChunkSize,
2143 ),
2144 (
2145 "zero envelope target size",
2146 CryptoHeaderFixed {
2147 envelope_target_size: 0,
2148 ..crypto_fixed()
2149 },
2150 FormatError::ZeroEnvelopeTargetSize,
2151 ),
2152 (
2153 "chunk exceeds envelope",
2154 CryptoHeaderFixed {
2155 chunk_size: 4096,
2156 envelope_target_size: 2048,
2157 ..crypto_fixed()
2158 },
2159 FormatError::ChunkSizeExceedsEnvelopeTarget {
2160 chunk_size: 4096,
2161 envelope_target_size: 2048,
2162 },
2163 ),
2164 (
2165 "block size too small",
2166 CryptoHeaderFixed {
2167 block_size: 2048,
2168 ..crypto_fixed()
2169 },
2170 FormatError::BlockSizeTooSmall(2048),
2171 ),
2172 ];
2173
2174 for (name, header, expected) in cases {
2175 assert_eq!(
2176 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2177 expected,
2178 "{name}"
2179 );
2180 }
2181 }
2182
2183 #[test]
2184 fn crypto_header_fixed_treats_expected_volume_size_as_advisory_not_reserved() {
2185 let mut header = crypto_fixed();
2186 header.expected_volume_size = 1u64 << 56;
2187
2188 let parsed = CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap();
2189
2190 assert_eq!(parsed.expected_volume_size, 1u64 << 56);
2191 }
2192
2193 #[test]
2194 fn crypto_header_fixed_rejects_reader_cap_excesses() {
2195 let mut header = crypto_fixed();
2196 header.chunk_size = READER_MAX_CHUNK_SIZE + 1;
2197 header.envelope_target_size = header.chunk_size;
2198 assert_eq!(
2199 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2200 FormatError::ReaderResourceLimitExceeded {
2201 field: "chunk_size",
2202 cap: READER_MAX_CHUNK_SIZE as u64,
2203 actual: (READER_MAX_CHUNK_SIZE + 1) as u64,
2204 }
2205 );
2206
2207 let mut header = crypto_fixed();
2208 header.block_size = READER_MAX_BLOCK_SIZE + 2;
2209 assert_eq!(
2210 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2211 FormatError::ReaderResourceLimitExceeded {
2212 field: "block_size",
2213 cap: READER_MAX_BLOCK_SIZE as u64,
2214 actual: (READER_MAX_BLOCK_SIZE + 2) as u64,
2215 }
2216 );
2217
2218 let mut header = crypto_fixed();
2219 header.max_path_length = READER_MAX_PATH_LENGTH + 1;
2220 assert_eq!(
2221 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2222 FormatError::ReaderResourceLimitExceeded {
2223 field: "max_path_length",
2224 cap: READER_MAX_PATH_LENGTH as u64,
2225 actual: (READER_MAX_PATH_LENGTH + 1) as u64,
2226 }
2227 );
2228
2229 let mut header = crypto_fixed();
2230 header.fec_data_shards = READER_MAX_FEC_CLASS_SHARDS as u16;
2231 header.fec_parity_shards = 1;
2232 assert_eq!(
2233 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2234 FormatError::ReaderResourceLimitExceeded {
2235 field: "fec_data_shards + fec_parity_shards",
2236 cap: READER_MAX_FEC_CLASS_SHARDS as u64,
2237 actual: (READER_MAX_FEC_CLASS_SHARDS + 1) as u64,
2238 }
2239 );
2240
2241 let mut header = crypto_fixed();
2242 header.index_fec_data_shards = READER_MAX_INDEX_FEC_CLASS_SHARDS as u16;
2243 header.index_fec_parity_shards = 1;
2244 assert_eq!(
2245 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2246 FormatError::ReaderResourceLimitExceeded {
2247 field: "index_fec_data_shards + index_fec_parity_shards",
2248 cap: READER_MAX_INDEX_FEC_CLASS_SHARDS as u64,
2249 actual: (READER_MAX_INDEX_FEC_CLASS_SHARDS + 1) as u64,
2250 }
2251 );
2252
2253 let mut header = crypto_fixed();
2254 header.block_size = 1_048_576;
2255 header.fec_data_shards = 4_096;
2256 header.fec_parity_shards = 0;
2257 let max_data_shards = u32::MAX as u64 / header.block_size as u64;
2258 assert_eq!(
2259 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2260 FormatError::ReaderResourceLimitExceeded {
2261 field: "fec_data_shards",
2262 cap: max_data_shards,
2263 actual: 4_096,
2264 }
2265 );
2266
2267 let mut header = crypto_fixed();
2268 header.block_size = 1_048_576;
2269 header.index_fec_data_shards = 4_096;
2270 header.index_fec_parity_shards = 0;
2271 let max_data_shards = u32::MAX as u64 / header.block_size as u64;
2272 assert_eq!(
2273 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2274 FormatError::ReaderResourceLimitExceeded {
2275 field: "index_fec_data_shards",
2276 cap: max_data_shards,
2277 actual: 4_096,
2278 }
2279 );
2280
2281 let mut header = crypto_fixed();
2282 header.block_size = 1_048_576;
2283 header.index_root_fec_data_shards = 4_096;
2284 let max_data_shards = u32::MAX as u64 / header.block_size as u64;
2285 assert_eq!(
2286 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2287 FormatError::ReaderResourceLimitExceeded {
2288 field: "index_root_fec_data_shards",
2289 cap: max_data_shards,
2290 actual: 4_096,
2291 }
2292 );
2293 }
2294
2295 #[test]
2296 fn crypto_extension_scanner_enforces_terminator_and_caps() {
2297 let bytes = [0u8; CRYPTO_EXTENSION_HEADER_LEN];
2298 assert!(scan_crypto_extension_tlvs(&bytes).unwrap().is_empty());
2299
2300 let mut bytes = Vec::new();
2301 bytes.extend_from_slice(&0x0001u16.to_le_bytes());
2302 bytes.extend_from_slice(&3u32.to_le_bytes());
2303 bytes.extend_from_slice(b"hey");
2304 bytes.extend_from_slice(&0u16.to_le_bytes());
2305 bytes.extend_from_slice(&0u32.to_le_bytes());
2306 let tlvs = scan_crypto_extension_tlvs(&bytes).unwrap();
2307 assert_eq!(tlvs[0].tag, 1);
2308 assert_eq!(tlvs[0].value, b"hey");
2309
2310 let mut bytes = Vec::new();
2311 bytes.extend_from_slice(&0x0001u16.to_le_bytes());
2312 bytes.extend_from_slice(&257u32.to_le_bytes());
2313 assert_eq!(
2314 scan_crypto_extension_tlvs(&bytes).unwrap_err(),
2315 FormatError::ExtensionPayloadTooLarge(257)
2316 );
2317
2318 assert_eq!(
2319 scan_crypto_extension_tlvs(&[]).unwrap_err(),
2320 FormatError::MissingExtensionTerminator
2321 );
2322 }
2323
2324 #[test]
2325 fn crypto_header_parse_splits_fixed_kdf_extensions_and_hmac() {
2326 let bytes = raw_crypto_header_bytes();
2327 let header = CryptoHeader::parse(&bytes, bytes.len() as u32).unwrap();
2328 assert_eq!(header.fixed.kdf_algo, KdfAlgo::Raw);
2329 assert_eq!(header.kdf_params, KdfParams::Raw);
2330 assert!(header.extensions.is_empty());
2331 assert_eq!(header.header_hmac, [0xab; CRYPTO_HEADER_HMAC_LEN]);
2332 assert_eq!(
2333 header.hmac_covered_bytes.len(),
2334 bytes.len() - CRYPTO_HEADER_HMAC_LEN
2335 );
2336 }
2337
2338 #[test]
2339 fn crypto_header_parse_rejects_truncated_and_bad_kdf_params() {
2340 let mut bytes = raw_crypto_header_bytes();
2341 bytes.truncate(CRYPTO_HEADER_FIXED_LEN + 1);
2342 assert_eq!(
2343 CryptoHeader::parse(&bytes, bytes.len() as u32).unwrap_err(),
2344 FormatError::CryptoHeaderTooShort {
2345 min: CRYPTO_HEADER_FIXED_LEN
2346 + 2
2347 + CRYPTO_EXTENSION_HEADER_LEN
2348 + CRYPTO_HEADER_HMAC_LEN,
2349 actual: CRYPTO_HEADER_FIXED_LEN + 1
2350 }
2351 );
2352
2353 let mut bytes = raw_crypto_header_bytes();
2354 write_u16(
2355 &mut bytes,
2356 CRYPTO_HEADER_FIXED_LEN,
2357 KdfAlgo::Argon2id as u16,
2358 );
2359 assert_eq!(
2360 CryptoHeader::parse(&bytes, bytes.len() as u32).unwrap_err(),
2361 FormatError::KdfAlgoTagMismatch {
2362 expected: 0,
2363 actual: 1
2364 }
2365 );
2366 }
2367
2368 #[test]
2369 fn crypto_header_length_mismatch_matrix_rejects_independent_declared_lengths() {
2370 let canonical = raw_crypto_header_bytes();
2371 CryptoHeader::parse(&canonical, canonical.len() as u32).unwrap();
2372
2373 let mut fixed_longer = canonical.clone();
2374 write_u32(&mut fixed_longer, 4, (canonical.len() + 1) as u32);
2375 assert_eq!(
2376 CryptoHeader::parse(&fixed_longer, fixed_longer.len() as u32).unwrap_err(),
2377 FormatError::CryptoHeaderLengthMismatch {
2378 fixed: (canonical.len() + 1) as u32,
2379 volume: canonical.len() as u32,
2380 }
2381 );
2382
2383 let longer_volume_len = (canonical.len() + 1) as u32;
2384 let mut padded = canonical.clone();
2385 padded.insert(canonical.len() - CRYPTO_HEADER_HMAC_LEN, 0);
2386 assert_eq!(
2387 CryptoHeader::parse(&padded, longer_volume_len).unwrap_err(),
2388 FormatError::CryptoHeaderLengthMismatch {
2389 fixed: canonical.len() as u32,
2390 volume: longer_volume_len,
2391 }
2392 );
2393
2394 let mut fixed_shorter = canonical.clone();
2395 write_u32(&mut fixed_shorter, 4, (canonical.len() - 1) as u32);
2396 assert_eq!(
2397 CryptoHeader::parse(&fixed_shorter, fixed_shorter.len() as u32).unwrap_err(),
2398 FormatError::CryptoHeaderLengthMismatch {
2399 fixed: (canonical.len() - 1) as u32,
2400 volume: canonical.len() as u32,
2401 }
2402 );
2403 }
2404
2405 #[test]
2406 fn crypto_extension_semantics_reject_forbidden_duplicate_and_critical() {
2407 let duplicate = vec![
2408 ExtensionTlv {
2409 tag: 0x0001,
2410 value: b"one",
2411 },
2412 ExtensionTlv {
2413 tag: 0x0001,
2414 value: b"two",
2415 },
2416 ];
2417 assert_eq!(
2418 validate_crypto_extension_semantics(&duplicate).unwrap_err(),
2419 FormatError::DuplicateKnownExtension(0x0001)
2420 );
2421
2422 let forbidden = vec![ExtensionTlv {
2423 tag: 0x8004,
2424 value: b"",
2425 }];
2426 assert_eq!(
2427 validate_crypto_extension_semantics(&forbidden).unwrap_err(),
2428 FormatError::ForbiddenExtensionTag(0x0004)
2429 );
2430
2431 let unknown_critical = vec![ExtensionTlv {
2432 tag: 0x8123,
2433 value: b"",
2434 }];
2435 assert_eq!(
2436 validate_crypto_extension_semantics(&unknown_critical).unwrap_err(),
2437 FormatError::UnknownCriticalExtension(0x0123)
2438 );
2439
2440 let malformed_known = vec![ExtensionTlv {
2441 tag: 0x0003,
2442 value: b"short",
2443 }];
2444 assert_eq!(
2445 validate_crypto_extension_semantics(&malformed_known).unwrap_err(),
2446 FormatError::MalformedKnownExtension(0x0003)
2447 );
2448 }
2449
2450 #[test]
2451 fn block_record_round_trips_and_validates_crc() {
2452 let record = BlockRecord {
2453 block_index: 0,
2454 kind: BlockKind::PayloadData,
2455 flags: BLOCK_LAST_DATA_FLAG,
2456 payload: vec![7; 4096],
2457 record_crc32c: 0,
2458 };
2459 let bytes = record.to_bytes();
2460 let parsed = BlockRecord::parse(&bytes, 4096).unwrap();
2461 assert_eq!(parsed.kind, BlockKind::PayloadData);
2462 assert!(parsed.is_last_data());
2463
2464 let mut corrupted = bytes;
2465 corrupted[20] ^= 1;
2466 assert_eq!(
2467 BlockRecord::parse(&corrupted, 4096).unwrap_err(),
2468 FormatError::BadCrc {
2469 structure: "BlockRecord"
2470 }
2471 );
2472 }
2473
2474 #[test]
2475 fn block_record_crc_covers_every_record_header_and_payload_byte() {
2476 let record = BlockRecord {
2477 block_index: 0x0102_0304_0506_0708,
2478 kind: BlockKind::PayloadData,
2479 flags: BLOCK_LAST_DATA_FLAG,
2480 payload: (0..4096).map(|idx| (idx & 0xff) as u8).collect(),
2481 record_crc32c: 0,
2482 };
2483 let bytes = record.to_bytes();
2484 let covered_len = 16 + record.payload.len();
2485
2486 for offset in 0..covered_len {
2487 let mut corrupted = bytes.clone();
2488 corrupted[offset] ^= 0x80;
2489 let err = BlockRecord::parse(&corrupted, record.payload.len()).unwrap_err();
2490 if offset < 4 {
2491 assert_eq!(
2492 err,
2493 FormatError::BadMagic {
2494 structure: "BlockRecord"
2495 },
2496 "magic byte {offset}"
2497 );
2498 } else if (14..16).contains(&offset) {
2499 assert_eq!(
2500 err,
2501 FormatError::NonZeroReserved {
2502 structure: "BlockRecord"
2503 },
2504 "reserved byte {offset}"
2505 );
2506 } else {
2507 assert_eq!(
2508 err,
2509 FormatError::BadCrc {
2510 structure: "BlockRecord"
2511 },
2512 "CRC-covered byte {offset}"
2513 );
2514 }
2515 }
2516
2517 let mut corrupted_crc = bytes;
2518 corrupted_crc[covered_len] ^= 0x80;
2519 assert_eq!(
2520 BlockRecord::parse(&corrupted_crc, record.payload.len()).unwrap_err(),
2521 FormatError::BadCrc {
2522 structure: "BlockRecord"
2523 }
2524 );
2525 }
2526
2527 #[test]
2528 fn block_record_rejects_reserved_kind_flags_and_parity_last_flag() {
2529 let mut record = BlockRecord {
2530 block_index: 0,
2531 kind: BlockKind::PayloadData,
2532 flags: 0x02,
2533 payload: vec![0; 4096],
2534 record_crc32c: 0,
2535 };
2536 assert_eq!(
2537 BlockRecord::parse(&record.to_bytes(), 4096).unwrap_err(),
2538 FormatError::InvalidBlockFlags(0x02)
2539 );
2540
2541 record.kind = BlockKind::PayloadParity;
2542 record.flags = BLOCK_LAST_DATA_FLAG;
2543 assert_eq!(
2544 BlockRecord::parse(&record.to_bytes(), 4096).unwrap_err(),
2545 FormatError::ParityBlockHasLastDataFlag
2546 );
2547
2548 let mut bytes = record.to_bytes();
2549 bytes[12] = 10;
2550 let crc = crc32c(&bytes[..4112]);
2551 write_u32(&mut bytes, 4112, crc);
2552 assert_eq!(
2553 BlockRecord::parse(&bytes, 4096).unwrap_err(),
2554 FormatError::UnknownBlockKind(10)
2555 );
2556
2557 for unknown_kind in 10..=u8::MAX {
2558 let mut bytes = record.to_bytes();
2559 bytes[12] = unknown_kind;
2560 let crc = crc32c(&bytes[..4112]);
2561 write_u32(&mut bytes, 4112, crc);
2562 assert_eq!(
2563 BlockRecord::parse(&bytes, 4096).unwrap_err(),
2564 FormatError::UnknownBlockKind(unknown_kind)
2565 );
2566 }
2567 }
2568
2569 #[test]
2570 fn manifest_footer_round_trips_and_validates_index_extent() {
2571 let footer = ManifestFooter {
2572 archive_uuid: uuid(),
2573 session_id: session(),
2574 volume_index: 0,
2575 is_authoritative: 1,
2576 total_volumes: 1,
2577 index_root_first_block: 0,
2578 index_root_data_block_count: 2,
2579 index_root_parity_block_count: 1,
2580 index_root_encrypted_size: 8192,
2581 index_root_decompressed_size: 120,
2582 manifest_hmac: [0xaa; 32],
2583 };
2584 let parsed = ManifestFooter::parse(&footer.to_bytes()).unwrap();
2585 parsed.validate_index_root_extent(4096).unwrap();
2586
2587 let mut bad = footer.clone();
2588 bad.index_root_encrypted_size = 4096;
2589 assert_eq!(
2590 ManifestFooter::parse(&bad.to_bytes())
2591 .unwrap()
2592 .validate_index_root_extent(4096)
2593 .unwrap_err(),
2594 FormatError::IndexRootSizeMismatch
2595 );
2596 }
2597
2598 #[test]
2599 fn volume_trailer_round_trips_and_requires_manifest_length() {
2600 let trailer = VolumeTrailer {
2601 archive_uuid: uuid(),
2602 session_id: session(),
2603 volume_index: 0,
2604 block_count: 3,
2605 bytes_written: 10_000,
2606 manifest_footer_offset: 9_864,
2607 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
2608 closed_at_ns: 123,
2609 root_auth_footer_offset: 0,
2610 root_auth_footer_length: 0,
2611 root_auth_flags: 0,
2612 trailer_hmac: [0xbb; 32],
2613 };
2614 let parsed = VolumeTrailer::parse(&trailer.to_bytes()).unwrap();
2615 assert_eq!(parsed.block_count, 3);
2616
2617 let mut bad = trailer;
2618 bad.manifest_footer_length = 100;
2619 assert_eq!(
2620 VolumeTrailer::parse(&bad.to_bytes()).unwrap_err(),
2621 FormatError::InvalidManifestFooterLength(100)
2622 );
2623 }
2624
2625 #[test]
2626 fn root_auth_footer_round_trips_and_validates_crc_and_lengths() {
2627 let footer = RootAuthFooterV1 {
2628 archive_uuid: uuid(),
2629 session_id: session(),
2630 authenticator_id: 2,
2631 signer_identity_type: 1,
2632 signer_identity_bytes: b"public-key".to_vec(),
2633 authenticator_value: vec![0x5a; 68],
2634 total_data_block_count: 7,
2635 critical_metadata_digest: [1; 32],
2636 index_digest: [2; 32],
2637 fec_layout_digest: [3; 32],
2638 data_block_merkle_root: [4; 32],
2639 signer_identity_digest: [5; 32],
2640 archive_root: [6; 32],
2641 footer_crc32c: 0,
2642 };
2643 let bytes = footer.to_bytes().unwrap();
2644 let parsed = RootAuthFooterV1::parse(&bytes).unwrap();
2645 assert_eq!(parsed.archive_uuid, uuid());
2646 assert_eq!(parsed.signer_identity_bytes, b"public-key");
2647 assert_eq!(parsed.authenticator_value, vec![0x5a; 68]);
2648 assert_eq!(parsed.footer_length().unwrap() as usize, bytes.len());
2649
2650 let mut bad_crc = bytes.clone();
2651 bad_crc[100] ^= 0x40;
2652 assert_eq!(
2653 RootAuthFooterV1::parse(&bad_crc).unwrap_err(),
2654 FormatError::BadCrc {
2655 structure: "RootAuthFooterV1"
2656 }
2657 );
2658
2659 let mut bad_len = bytes;
2660 write_u32(&mut bad_len, 30, 1);
2661 let crc_offset = bad_len.len() - 4;
2662 let crc = crc32c(&bad_len[..crc_offset]);
2663 write_u32(&mut bad_len, crc_offset, crc);
2664 assert!(matches!(
2665 RootAuthFooterV1::parse(&bad_len).unwrap_err(),
2666 FormatError::InvalidLength {
2667 structure: "RootAuthFooterV1",
2668 ..
2669 }
2670 ));
2671 }
2672
2673 #[test]
2674 fn bootstrap_sidecar_header_validates_presence_and_layout() {
2675 let header = BootstrapSidecarHeader {
2676 archive_uuid: uuid(),
2677 session_id: session(),
2678 flags: SIDECAR_MANIFEST_PRESENT | SIDECAR_INDEX_ROOT_PRESENT,
2679 manifest_footer_offset: BOOTSTRAP_SIDECAR_HEADER_LEN as u64,
2680 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
2681 index_root_records_offset: (BOOTSTRAP_SIDECAR_HEADER_LEN + MANIFEST_FOOTER_LEN) as u64,
2682 index_root_records_length: 40,
2683 dictionary_records_offset: 0,
2684 dictionary_records_length: 0,
2685 sidecar_hmac: [0xcc; 32],
2686 header_crc32c: 0,
2687 };
2688 let parsed = BootstrapSidecarHeader::parse(&header.to_bytes()).unwrap();
2689 parsed
2690 .validate_packed_layout(
2691 BOOTSTRAP_SIDECAR_HEADER_LEN as u64 + MANIFEST_FOOTER_LEN as u64 + 40,
2692 )
2693 .unwrap();
2694
2695 let mut bad = header.clone();
2696 bad.flags |= 0x08;
2697 assert_eq!(
2698 BootstrapSidecarHeader::parse(&bad.to_bytes()).unwrap_err(),
2699 FormatError::UnknownBootstrapSidecarFlags(0x0b)
2700 );
2701
2702 let mut bad = header;
2703 bad.index_root_records_offset += 1;
2704 let parsed = BootstrapSidecarHeader::parse(&bad.to_bytes()).unwrap();
2705 assert_eq!(
2706 parsed
2707 .validate_packed_layout(
2708 BOOTSTRAP_SIDECAR_HEADER_LEN as u64 + MANIFEST_FOOTER_LEN as u64 + 40
2709 )
2710 .unwrap_err(),
2711 FormatError::NonCanonicalBootstrapSidecarLayout
2712 );
2713 }
2714}