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