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