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