1use crc32c::crc32c;
2use sha2::{Digest, Sha256};
3
4use crate::crypto::KdfParams;
5use crate::format::{
6 root_auth_spec_id_for_revision, AeadAlgo, BlockKind, CompressionAlgo, FecAlgo, FormatError,
7 KdfAlgo, VolumeFormatRevision, BLOCK_RECORD_FRAMING_LEN, BOOTSTRAP_SIDECAR_HEADER_LEN,
8 CRITICAL_METADATA_IMAGE_FIXED_LEN, CRITICAL_METADATA_IMAGE_FIXED_LEN_V43,
9 CRITICAL_METADATA_RECOVERY_HEADER_LEN, CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN,
10 CRITICAL_RECOVERY_LOCATOR_LEN, CRYPTO_EXTENSION_HEADER_LEN, CRYPTO_EXTENSION_MAX_VALUE_LEN,
11 CRYPTO_HEADER_FIXED_LEN, CRYPTO_HEADER_HMAC_LEN, FORMAT_VERSION, IMAGE_CRC_LEN,
12 MANIFEST_FOOTER_LEN, READER_MAX_BLOCK_SIZE, READER_MAX_CHUNK_SIZE,
13 READER_MAX_CRYPTO_HEADER_LEN, READER_MAX_ENVELOPE_TARGET_SIZE, READER_MAX_FEC_CLASS_SHARDS,
14 READER_MAX_INDEX_FEC_CLASS_SHARDS, READER_MAX_INDEX_ROOT_FEC_CLASS_SHARDS,
15 READER_MAX_KEY_WRAP_TABLE_LEN, READER_MAX_KEY_WRAP_TABLE_RECIPIENT_RECORDS,
16 READER_MAX_PATH_LENGTH, READER_MAX_ROOT_AUTH_AUTHENTICATOR_VALUE_LEN,
17 READER_MAX_ROOT_AUTH_FOOTER_LEN, READER_MAX_ROOT_AUTH_SIGNER_IDENTITY_LEN,
18 READER_MAX_STRIPE_WIDTH, READER_MAX_SUPPORTED_VOLUME_FORMAT_REV, ROOT_AUTH_FOOTER_FIXED_LEN,
19 SERIALIZED_REGION_HEADER_LEN, VOLUME_FORMAT_REV_43, VOLUME_FORMAT_REV_44, VOLUME_HEADER_LEN,
20 VOLUME_TRAILER_LEN,
21};
22use crate::raw_stream_profile::{
23 validate_raw_stream_content_model_extension, RAW_STREAM_CONTENT_MODEL_EXTENSION_TAG,
24};
25
26const TZAP_MAGIC: [u8; 4] = *b"TZAP";
27const TZCH_MAGIC: [u8; 4] = *b"TZCH";
28const TZBK_MAGIC: [u8; 4] = *b"TZBK";
29const TZMF_MAGIC: [u8; 4] = *b"TZMF";
30const TZVT_MAGIC: [u8; 4] = *b"TZVT";
31const TZRA_MAGIC: [u8; 4] = *b"TZRA";
32const TZBS_MAGIC: [u8; 4] = *b"TZBS";
33const TZMI_MAGIC: [u8; 4] = *b"TZMI";
34const TZCR_MAGIC: [u8; 4] = *b"TZCR";
35const TZCS_MAGIC: [u8; 4] = *b"TZCS";
36const TZCL_MAGIC: [u8; 4] = *b"TZCL";
37const TZKW_MAGIC: [u8; 4] = *b"TZKW";
38
39const BLOCK_LAST_DATA_FLAG: u8 = 0x01;
40const BLOCK_RESERVED_FLAGS: u8 = !BLOCK_LAST_DATA_FLAG;
41
42const SIDECAR_MANIFEST_PRESENT: u32 = 0x01;
43const SIDECAR_INDEX_ROOT_PRESENT: u32 = 0x02;
44const SIDECAR_DICTIONARY_PRESENT: u32 = 0x04;
45const SIDECAR_KNOWN_FLAGS: u32 =
46 SIDECAR_MANIFEST_PRESENT | SIDECAR_INDEX_ROOT_PRESENT | SIDECAR_DICTIONARY_PRESENT;
47
48const KEY_WRAP_TABLE_DIGEST_DOMAIN_V44: &[u8] = b"tzap-key-wrap-table-v44\0";
49const KEY_WRAP_TABLE_VERSION: u16 = 1;
50const KEY_WRAP_TABLE_HEADER_LEN: usize = 96;
51const KEY_WRAP_RECORD_HEADER_LEN: usize = 68;
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct VolumeHeader {
55 pub format_version: u16,
56 pub volume_format_rev: u16,
57 pub volume_index: u32,
58 pub stripe_width: u32,
59 pub archive_uuid: [u8; 16],
60 pub session_id: [u8; 16],
61 pub crypto_header_offset: u32,
62 pub crypto_header_length: u32,
63 pub header_crc32c: u32,
64}
65
66impl VolumeHeader {
67 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
68 expect_len("VolumeHeader", VOLUME_HEADER_LEN, bytes.len())?;
69 expect_magic("VolumeHeader", TZAP_MAGIC, &bytes[0..4])?;
70 expect_crc("VolumeHeader", &bytes[..124], read_u32(bytes, 124)?)?;
71 expect_zero("VolumeHeader", &bytes[56..124])?;
72
73 let header = Self {
74 format_version: read_u16(bytes, 4)?,
75 volume_format_rev: read_u16(bytes, 6)?,
76 volume_index: read_u32(bytes, 8)?,
77 stripe_width: read_u32(bytes, 12)?,
78 archive_uuid: read_array_16(bytes, 16)?,
79 session_id: read_array_16(bytes, 32)?,
80 crypto_header_offset: read_u32(bytes, 48)?,
81 crypto_header_length: read_u32(bytes, 52)?,
82 header_crc32c: read_u32(bytes, 124)?,
83 };
84 header.validate()?;
85 Ok(header)
86 }
87
88 pub fn parse_volume_format_revision(&self) -> Result<VolumeFormatRevision, FormatError> {
89 VolumeFormatRevision::from_u16(self.volume_format_rev).ok_or({
90 FormatError::UnsupportedVolumeFormatRevision {
91 format_version: self.format_version,
92 volume_format_rev: self.volume_format_rev,
93 reader_max_supported_revision: READER_MAX_SUPPORTED_VOLUME_FORMAT_REV,
94 }
95 })
96 }
97
98 pub fn validate(&self) -> Result<(), FormatError> {
99 if self.format_version != FORMAT_VERSION {
100 return Err(FormatError::UnsupportedFormatVersion(self.format_version));
101 }
102 if self.stripe_width == 0 {
103 return Err(FormatError::ZeroStripeWidth);
104 }
105 if self.volume_index >= self.stripe_width {
106 return Err(FormatError::VolumeIndexOutOfRange {
107 volume_index: self.volume_index,
108 stripe_width: self.stripe_width,
109 });
110 }
111 if self.crypto_header_offset != VOLUME_HEADER_LEN as u32 {
112 return Err(FormatError::NonCanonicalCryptoHeaderOffset(
113 self.crypto_header_offset,
114 ));
115 }
116 if self.crypto_header_length > READER_MAX_CRYPTO_HEADER_LEN {
117 return Err(FormatError::ReaderResourceLimitExceeded {
118 field: "CryptoHeader length",
119 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
120 actual: self.crypto_header_length as u64,
121 });
122 }
123 Ok(())
124 }
125
126 pub fn to_bytes(&self) -> [u8; VOLUME_HEADER_LEN] {
127 let mut bytes = [0u8; VOLUME_HEADER_LEN];
128 bytes[0..4].copy_from_slice(&TZAP_MAGIC);
129 write_u16(&mut bytes, 4, self.format_version);
130 write_u16(&mut bytes, 6, self.volume_format_rev);
131 write_u32(&mut bytes, 8, self.volume_index);
132 write_u32(&mut bytes, 12, self.stripe_width);
133 bytes[16..32].copy_from_slice(&self.archive_uuid);
134 bytes[32..48].copy_from_slice(&self.session_id);
135 write_u32(&mut bytes, 48, self.crypto_header_offset);
136 write_u32(&mut bytes, 52, self.crypto_header_length);
137 let crc = crc32c(&bytes[..124]);
138 write_u32(&mut bytes, 124, crc);
139 bytes
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct CryptoHeaderFixed {
145 pub length: u32,
146 pub compression_algo: CompressionAlgo,
147 pub aead_algo: AeadAlgo,
148 pub fec_algo: FecAlgo,
149 pub kdf_algo: KdfAlgo,
150 pub chunk_size: u32,
151 pub envelope_target_size: u32,
152 pub block_size: u32,
153 pub fec_data_shards: u16,
154 pub fec_parity_shards: u16,
155 pub index_fec_data_shards: u16,
156 pub index_fec_parity_shards: u16,
157 pub index_root_fec_data_shards: u16,
158 pub index_root_fec_parity_shards: u16,
159 pub stripe_width: u32,
160 pub volume_loss_tolerance: u8,
161 pub bit_rot_buffer_pct: u8,
162 pub has_dictionary: u8,
163 pub max_path_length: u32,
164 pub expected_volume_size: u64,
165}
166
167impl CryptoHeaderFixed {
168 pub fn parse(bytes: &[u8], volume_crypto_header_length: u32) -> Result<Self, FormatError> {
169 expect_len("CryptoHeaderFixed", CRYPTO_HEADER_FIXED_LEN, bytes.len())?;
170 expect_magic("CryptoHeaderFixed", TZCH_MAGIC, &bytes[0..4])?;
171 expect_zero("CryptoHeaderFixed", &bytes[47..48])?;
172 expect_zero("CryptoHeaderFixed", &bytes[60..76])?;
173
174 let length = read_u32(bytes, 4)?;
175 if length != volume_crypto_header_length {
176 return Err(FormatError::CryptoHeaderLengthMismatch {
177 fixed: length,
178 volume: volume_crypto_header_length,
179 });
180 }
181 if length > READER_MAX_CRYPTO_HEADER_LEN {
182 return Err(FormatError::ReaderResourceLimitExceeded {
183 field: "CryptoHeader length",
184 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
185 actual: length as u64,
186 });
187 }
188
189 let header = Self {
190 length,
191 compression_algo: CompressionAlgo::try_from(read_u16(bytes, 8)?)?,
192 aead_algo: AeadAlgo::try_from(read_u16(bytes, 10)?)?,
193 fec_algo: FecAlgo::try_from(read_u16(bytes, 12)?)?,
194 kdf_algo: KdfAlgo::try_from(read_u16(bytes, 14)?)?,
195 chunk_size: read_u32(bytes, 16)?,
196 envelope_target_size: read_u32(bytes, 20)?,
197 block_size: read_u32(bytes, 24)?,
198 fec_data_shards: read_u16(bytes, 28)?,
199 fec_parity_shards: read_u16(bytes, 30)?,
200 index_fec_data_shards: read_u16(bytes, 32)?,
201 index_fec_parity_shards: read_u16(bytes, 34)?,
202 index_root_fec_data_shards: read_u16(bytes, 36)?,
203 index_root_fec_parity_shards: read_u16(bytes, 38)?,
204 stripe_width: read_u32(bytes, 40)?,
205 volume_loss_tolerance: bytes[44],
206 bit_rot_buffer_pct: bytes[45],
207 has_dictionary: bytes[46],
208 max_path_length: read_u32(bytes, 48)?,
209 expected_volume_size: read_u64(bytes, 52)?,
210 };
211 header.validate_supported_profile()?;
212 Ok(header)
213 }
214
215 pub fn validate_supported_profile(&self) -> Result<(), FormatError> {
216 if self.compression_algo != CompressionAlgo::ZstdFramed {
217 return Err(FormatError::UnsupportedCompression(self.compression_algo));
218 }
219 if self.fec_algo != FecAlgo::ReedSolomonGF16 {
220 return Err(FormatError::UnsupportedFec(self.fec_algo));
221 }
222 match (self.aead_algo, self.kdf_algo) {
223 (AeadAlgo::None, KdfAlgo::None) => {}
224 (aead_algo, KdfAlgo::Raw | KdfAlgo::Argon2id | KdfAlgo::RecipientWrap)
225 if aead_algo.is_encrypted() => {}
226 _ => {
227 return Err(FormatError::InvalidProtectionMode {
228 aead_algo: self.aead_algo,
229 kdf_algo: self.kdf_algo,
230 });
231 }
232 }
233 if self.has_dictionary > 1 {
234 return Err(FormatError::InvalidBoolean {
235 field: "has_dictionary",
236 value: self.has_dictionary,
237 });
238 }
239 if self.stripe_width == 0 {
240 return Err(FormatError::ZeroStripeWidth);
241 }
242 if self.stripe_width > READER_MAX_STRIPE_WIDTH {
243 return Err(FormatError::ReaderResourceLimitExceeded {
244 field: "stripe_width",
245 cap: READER_MAX_STRIPE_WIDTH as u64,
246 actual: self.stripe_width as u64,
247 });
248 }
249 if self.volume_loss_tolerance as u32 >= self.stripe_width {
250 return Err(FormatError::VolumeLossToleranceOutOfRange {
251 volume_loss_tolerance: self.volume_loss_tolerance,
252 stripe_width: self.stripe_width,
253 });
254 }
255 if self.bit_rot_buffer_pct > 100 {
256 return Err(FormatError::BitRotBufferPctTooLarge(
257 self.bit_rot_buffer_pct,
258 ));
259 }
260 if self.fec_data_shards == 0 {
261 return Err(FormatError::ZeroDataShardMaximum {
262 field: "fec_data_shards",
263 });
264 }
265 if self.index_fec_data_shards == 0 {
266 return Err(FormatError::ZeroDataShardMaximum {
267 field: "index_fec_data_shards",
268 });
269 }
270 if self.index_root_fec_data_shards == 0 {
271 return Err(FormatError::ZeroDataShardMaximum {
272 field: "index_root_fec_data_shards",
273 });
274 }
275 validate_fec_class_shards(
276 "fec_data_shards + fec_parity_shards",
277 self.fec_data_shards,
278 self.fec_parity_shards,
279 READER_MAX_FEC_CLASS_SHARDS,
280 )?;
281 validate_fec_class_shards(
282 "index_fec_data_shards + index_fec_parity_shards",
283 self.index_fec_data_shards,
284 self.index_fec_parity_shards,
285 READER_MAX_INDEX_FEC_CLASS_SHARDS,
286 )?;
287 validate_fec_class_shards(
288 "index_root_fec_data_shards + index_root_fec_parity_shards",
289 self.index_root_fec_data_shards,
290 self.index_root_fec_parity_shards,
291 READER_MAX_INDEX_ROOT_FEC_CLASS_SHARDS,
292 )?;
293 if self.chunk_size == 0 {
294 return Err(FormatError::ZeroChunkSize);
295 }
296 if self.envelope_target_size == 0 {
297 return Err(FormatError::ZeroEnvelopeTargetSize);
298 }
299 if self.chunk_size > self.envelope_target_size {
300 return Err(FormatError::ChunkSizeExceedsEnvelopeTarget {
301 chunk_size: self.chunk_size,
302 envelope_target_size: self.envelope_target_size,
303 });
304 }
305 if self.chunk_size > READER_MAX_CHUNK_SIZE {
306 return Err(FormatError::ReaderResourceLimitExceeded {
307 field: "chunk_size",
308 cap: READER_MAX_CHUNK_SIZE as u64,
309 actual: self.chunk_size as u64,
310 });
311 }
312 if self.envelope_target_size > READER_MAX_ENVELOPE_TARGET_SIZE {
313 return Err(FormatError::ReaderResourceLimitExceeded {
314 field: "envelope_target_size",
315 cap: READER_MAX_ENVELOPE_TARGET_SIZE as u64,
316 actual: self.envelope_target_size as u64,
317 });
318 }
319 if self.block_size < 4096 {
320 return Err(FormatError::BlockSizeTooSmall(self.block_size));
321 }
322 if self.block_size % 2 != 0 {
323 return Err(FormatError::OddBlockSize(self.block_size));
324 }
325 validate_fec_class_data_shards("fec_data_shards", self.fec_data_shards, self.block_size)?;
326 validate_fec_class_data_shards(
327 "index_fec_data_shards",
328 self.index_fec_data_shards,
329 self.block_size,
330 )?;
331 validate_fec_class_data_shards(
332 "index_root_fec_data_shards",
333 self.index_root_fec_data_shards,
334 self.block_size,
335 )?;
336 if self.block_size > READER_MAX_BLOCK_SIZE {
337 return Err(FormatError::ReaderResourceLimitExceeded {
338 field: "block_size",
339 cap: READER_MAX_BLOCK_SIZE as u64,
340 actual: self.block_size as u64,
341 });
342 }
343 if self.max_path_length > READER_MAX_PATH_LENGTH {
344 return Err(FormatError::ReaderResourceLimitExceeded {
345 field: "max_path_length",
346 cap: READER_MAX_PATH_LENGTH as u64,
347 actual: self.max_path_length as u64,
348 });
349 }
350 Ok(())
351 }
352
353 pub fn to_bytes(&self) -> [u8; CRYPTO_HEADER_FIXED_LEN] {
354 let mut bytes = [0u8; CRYPTO_HEADER_FIXED_LEN];
355 bytes[0..4].copy_from_slice(&TZCH_MAGIC);
356 write_u32(&mut bytes, 4, self.length);
357 write_u16(&mut bytes, 8, self.compression_algo as u16);
358 write_u16(&mut bytes, 10, self.aead_algo as u16);
359 write_u16(&mut bytes, 12, self.fec_algo as u16);
360 write_u16(&mut bytes, 14, self.kdf_algo as u16);
361 write_u32(&mut bytes, 16, self.chunk_size);
362 write_u32(&mut bytes, 20, self.envelope_target_size);
363 write_u32(&mut bytes, 24, self.block_size);
364 write_u16(&mut bytes, 28, self.fec_data_shards);
365 write_u16(&mut bytes, 30, self.fec_parity_shards);
366 write_u16(&mut bytes, 32, self.index_fec_data_shards);
367 write_u16(&mut bytes, 34, self.index_fec_parity_shards);
368 write_u16(&mut bytes, 36, self.index_root_fec_data_shards);
369 write_u16(&mut bytes, 38, self.index_root_fec_parity_shards);
370 write_u32(&mut bytes, 40, self.stripe_width);
371 bytes[44] = self.volume_loss_tolerance;
372 bytes[45] = self.bit_rot_buffer_pct;
373 bytes[46] = self.has_dictionary;
374 write_u32(&mut bytes, 48, self.max_path_length);
375 write_u64(&mut bytes, 52, self.expected_volume_size);
376 bytes
377 }
378}
379
380fn validate_fec_class_shards(
381 field: &'static str,
382 data_shards: u16,
383 parity_shards: u16,
384 cap: u32,
385) -> Result<(), FormatError> {
386 let total = data_shards as u32 + parity_shards as u32;
387 if total > cap {
388 return Err(FormatError::ReaderResourceLimitExceeded {
389 field,
390 cap: cap as u64,
391 actual: total as u64,
392 });
393 }
394 Ok(())
395}
396
397fn validate_fec_class_data_shards(
398 field: &'static str,
399 data_shards: u16,
400 block_size: u32,
401) -> Result<(), FormatError> {
402 let max_data_shards = u32::MAX as u64 / block_size as u64;
403 if (data_shards as u64) > max_data_shards {
404 return Err(FormatError::ReaderResourceLimitExceeded {
405 field,
406 cap: max_data_shards,
407 actual: data_shards as u64,
408 });
409 }
410 Ok(())
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
414pub struct ExtensionTlv<'a> {
415 pub tag: u16,
416 pub value: &'a [u8],
417}
418
419pub fn scan_crypto_extension_tlvs(bytes: &[u8]) -> Result<Vec<ExtensionTlv<'_>>, FormatError> {
420 let mut offset = 0usize;
421 let mut extensions = Vec::new();
422 loop {
423 if offset == bytes.len() {
424 return Err(FormatError::MissingExtensionTerminator);
425 }
426 if bytes.len() - offset < CRYPTO_EXTENSION_HEADER_LEN {
427 return Err(FormatError::TruncatedExtensionHeader);
428 }
429 let tag = read_u16(bytes, offset)?;
430 let length = read_u32(bytes, offset + 2)?;
431 offset += CRYPTO_EXTENSION_HEADER_LEN;
432 if tag == 0 {
433 if length != 0 {
434 return Err(FormatError::MalformedExtensionTerminator);
435 }
436 if offset != bytes.len() {
437 return Err(FormatError::BytesAfterExtensionTerminator);
438 }
439 return Ok(extensions);
440 }
441 if length > CRYPTO_EXTENSION_MAX_VALUE_LEN {
442 return Err(FormatError::ExtensionPayloadTooLarge(length));
443 }
444 let length = length as usize;
445 if bytes.len() - offset < length {
446 return Err(FormatError::TruncatedExtensionPayload);
447 }
448 extensions.push(ExtensionTlv {
449 tag,
450 value: &bytes[offset..offset + length],
451 });
452 offset += length;
453 }
454}
455
456pub fn validate_crypto_extension_semantics(
457 extensions: &[ExtensionTlv<'_>],
458) -> Result<(), FormatError> {
459 let mut seen_known = Vec::new();
460 for extension in extensions {
461 let ext_tag = extension.tag & 0x7fff;
462 let is_critical = extension.tag & 0x8000 != 0;
463 if matches!(ext_tag, 0x0004 | 0x0006) {
464 return Err(FormatError::ForbiddenExtensionTag(ext_tag));
465 }
466 if ext_tag == RAW_STREAM_CONTENT_MODEL_EXTENSION_TAG {
467 if is_critical {
468 if seen_known.contains(&ext_tag) {
469 return Err(FormatError::DuplicateKnownExtension(ext_tag));
470 }
471 validate_raw_stream_content_model_extension(is_critical, extension.value)?;
472 seen_known.push(ext_tag);
473 }
474 continue;
475 }
476 if is_known_extension(ext_tag) {
477 if seen_known.contains(&ext_tag) {
478 return Err(FormatError::DuplicateKnownExtension(ext_tag));
479 }
480 validate_known_extension(ext_tag, extension.value)?;
481 seen_known.push(ext_tag);
482 } else if is_critical {
483 return Err(FormatError::UnknownCriticalExtension(ext_tag));
484 }
485 }
486 Ok(())
487}
488
489#[derive(Debug, Clone, PartialEq, Eq)]
490pub struct CryptoHeader<'a> {
491 pub fixed: CryptoHeaderFixed,
492 pub kdf_params: KdfParams,
493 pub extensions: Vec<ExtensionTlv<'a>>,
494 pub header_hmac: [u8; 32],
495 pub hmac_covered_bytes: &'a [u8],
496}
497
498pub fn compute_key_wrap_table_digest(
499 key_wrap_table_length: u32,
500 key_wrap_table_bytes: &[u8],
501) -> [u8; 32] {
502 let mut hasher = Sha256::new();
503 hasher.update(KEY_WRAP_TABLE_DIGEST_DOMAIN_V44);
504 hasher.update(key_wrap_table_length.to_le_bytes());
505 hasher.update(key_wrap_table_bytes);
506 let digest = hasher.finalize();
507 let mut output = [0u8; 32];
508 output.copy_from_slice(&digest);
509 output
510}
511
512#[derive(Debug, Clone, PartialEq, Eq)]
513pub struct KeyWrapTableV1 {
514 pub version: u16,
515 pub volume_format_rev: u16,
516 pub table_length: u32,
517 pub flags: u32,
518 pub archive_uuid: [u8; 16],
519 pub session_id: [u8; 16],
520 pub recipient_record_count: u32,
521 pub records_offset: u32,
522 pub records_length: u32,
523 pub recipient_records: Vec<RecipientRecordV1>,
524}
525
526impl KeyWrapTableV1 {
527 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
528 if self.version != KEY_WRAP_TABLE_VERSION {
529 return Err(FormatError::InvalidArchive(
530 "KeyWrapTableV1 version must be 1",
531 ));
532 }
533 if self.volume_format_rev != VOLUME_FORMAT_REV_44 {
534 return Err(FormatError::InvalidArchive(
535 "KeyWrapTableV1 volume_format_rev must be 44",
536 ));
537 }
538 if self.flags != 0 {
539 return Err(FormatError::NonZeroReserved {
540 structure: "KeyWrapTableV1",
541 });
542 }
543 if self.records_offset != KEY_WRAP_TABLE_HEADER_LEN as u32 {
544 return Err(FormatError::InvalidArchive(
545 "KeyWrapTableV1 records_offset must be 96",
546 ));
547 }
548 if !self.recipient_records.is_empty() {
549 let mut recipients = Vec::with_capacity(self.recipient_records.len());
550 for record in &self.recipient_records {
551 recipients.extend_from_slice(&record.to_bytes()?);
552 }
553 let recipient_record_count = u32_len(
554 self.recipient_records.len(),
555 "KeyWrapTableV1 recipient_record_count",
556 )?;
557 if self.recipient_record_count != 0
558 && self.recipient_record_count != recipient_record_count
559 {
560 return Err(FormatError::InvalidArchive(
561 "KeyWrapTableV1 recipient_record_count does not match fields",
562 ));
563 }
564 let records_length = u32_len(recipients.len(), "KeyWrapTableV1 records_length")?;
565 if self.records_length != 0 && self.records_length != records_length {
566 return Err(FormatError::InvalidArchive(
567 "KeyWrapTableV1 records_length does not match fields",
568 ));
569 }
570
571 let table_length = u32_len(
572 KEY_WRAP_TABLE_HEADER_LEN
573 .checked_add(recipients.len())
574 .ok_or(FormatError::WriterUnsupported(
575 "key-wrap table length overflow",
576 ))?,
577 "KeyWrapTableV1 table_length",
578 )?;
579 if self.table_length != 0 && self.table_length != table_length {
580 return Err(FormatError::InvalidArchive(
581 "KeyWrapTableV1 table_length does not match fields",
582 ));
583 }
584
585 let mut bytes = vec![0u8; KEY_WRAP_TABLE_HEADER_LEN];
586 bytes[0..4].copy_from_slice(&TZKW_MAGIC);
587 write_u16(&mut bytes, 4, self.version);
588 write_u16(&mut bytes, 6, self.volume_format_rev);
589 write_u32(&mut bytes, 8, table_length);
590 write_u32(&mut bytes, 12, KEY_WRAP_TABLE_HEADER_LEN as u32);
591 bytes[20..36].copy_from_slice(&self.archive_uuid);
592 bytes[36..52].copy_from_slice(&self.session_id);
593 write_u32(&mut bytes, 52, recipient_record_count);
594 write_u32(&mut bytes, 56, KEY_WRAP_TABLE_HEADER_LEN as u32);
595 write_u32(&mut bytes, 60, records_length);
596 bytes.extend(recipients);
597 Ok(bytes)
598 } else {
599 Err(FormatError::WriterUnsupported(
600 "KeyWrapTableV1 must include at least one recipient record",
601 ))
602 }
603 }
604
605 pub fn parse(
606 bytes: &[u8],
607 archive_uuid: &[u8; 16],
608 session_id: &[u8; 16],
609 key_wrap_table_length: u32,
610 key_wrap_table_record_count: u32,
611 ) -> Result<Self, FormatError> {
612 if bytes.len() > READER_MAX_KEY_WRAP_TABLE_LEN as usize {
613 return Err(FormatError::ReaderResourceLimitExceeded {
614 field: "KeyWrapTableV1 length",
615 cap: READER_MAX_KEY_WRAP_TABLE_LEN as u64,
616 actual: bytes.len() as u64,
617 });
618 }
619 if bytes.len() < KEY_WRAP_TABLE_HEADER_LEN {
620 return Err(FormatError::InvalidLength {
621 structure: "KeyWrapTableV1",
622 expected: KEY_WRAP_TABLE_HEADER_LEN,
623 actual: bytes.len(),
624 });
625 }
626 expect_magic("KeyWrapTableV1", TZKW_MAGIC, &bytes[0..4])?;
627
628 let header_length = read_u32(bytes, 12)?;
629 if header_length != KEY_WRAP_TABLE_HEADER_LEN as u32 {
630 return Err(FormatError::InvalidArchive(
631 "KeyWrapTableV1 header_length must be 96",
632 ));
633 }
634 let version = read_u16(bytes, 4)?;
635 if version != KEY_WRAP_TABLE_VERSION {
636 return Err(FormatError::InvalidArchive(
637 "KeyWrapTableV1 version must be 1",
638 ));
639 }
640 let volume_format_rev = read_u16(bytes, 6)?;
641 if volume_format_rev != VOLUME_FORMAT_REV_44 {
642 return Err(FormatError::InvalidArchive(
643 "KeyWrapTableV1 volume_format_rev must be 44",
644 ));
645 }
646
647 let declared_length = read_u32(bytes, 8)?;
648 if declared_length != key_wrap_table_length {
649 return Err(FormatError::InvalidArchive(
650 "KeyWrapTableV1 length does not match KdfParams",
651 ));
652 }
653 if declared_length as usize != bytes.len() {
654 return Err(FormatError::InvalidLength {
655 structure: "KeyWrapTableV1",
656 expected: declared_length as usize,
657 actual: bytes.len(),
658 });
659 }
660
661 let flags = read_u32(bytes, 16)?;
662 if flags != 0 {
663 return Err(FormatError::NonZeroReserved {
664 structure: "KeyWrapTableV1",
665 });
666 }
667
668 let table_archive_uuid = read_array_16(bytes, 20)?;
669 if table_archive_uuid != *archive_uuid {
670 return Err(FormatError::InvalidArchive(
671 "KeyWrapTableV1 archive_uuid does not match CryptoHeader",
672 ));
673 }
674 let table_session_id = read_array_16(bytes, 36)?;
675 if table_session_id != *session_id {
676 return Err(FormatError::InvalidArchive(
677 "KeyWrapTableV1 session_id does not match CryptoHeader",
678 ));
679 }
680
681 let recipient_record_count = read_u32(bytes, 52)?;
682 if recipient_record_count > READER_MAX_KEY_WRAP_TABLE_RECIPIENT_RECORDS {
683 return Err(FormatError::ReaderResourceLimitExceeded {
684 field: "KeyWrapTableV1 recipient_record_count",
685 cap: READER_MAX_KEY_WRAP_TABLE_RECIPIENT_RECORDS as u64,
686 actual: recipient_record_count as u64,
687 });
688 }
689 if recipient_record_count != key_wrap_table_record_count {
690 return Err(FormatError::InvalidArchive(
691 "KeyWrapTableV1 recipient_record_count does not match KdfParams",
692 ));
693 }
694
695 let records_offset = read_u32(bytes, 56)?;
696 let records_length = read_u32(bytes, 60)?;
697 if records_offset != KEY_WRAP_TABLE_HEADER_LEN as u32 {
698 return Err(FormatError::InvalidArchive(
699 "KeyWrapTableV1 records_offset must be 96",
700 ));
701 }
702 let records_end =
703 records_offset
704 .checked_add(records_length)
705 .ok_or(FormatError::InvalidArchive(
706 "KeyWrapTableV1 records_offset + records_length overflow",
707 ))?;
708 if records_end != declared_length {
709 return Err(FormatError::InvalidArchive(
710 "KeyWrapTableV1 records_offset and records_length are inconsistent",
711 ));
712 }
713 expect_zero("KeyWrapTableV1", &bytes[64..96])?;
714
715 let mut recipient_records = Vec::with_capacity(recipient_record_count as usize);
716 let mut cursor = records_offset as usize;
717 for _ in 0..recipient_record_count {
718 let (record, consumed) = RecipientRecordV1::parse(&bytes[cursor..])?;
719 cursor += consumed;
720 recipient_records.push(record);
721 }
722 if cursor != declared_length as usize {
723 return Err(FormatError::InvalidArchive(
724 "KeyWrapTableV1 has trailing record bytes",
725 ));
726 }
727
728 Ok(Self {
729 version,
730 volume_format_rev,
731 table_length: declared_length,
732 flags,
733 archive_uuid: table_archive_uuid,
734 session_id: table_session_id,
735 recipient_record_count,
736 records_offset,
737 records_length,
738 recipient_records,
739 })
740 }
741}
742
743#[derive(Debug, Clone, PartialEq, Eq)]
744pub struct RecipientRecordV1 {
745 pub record_length: u32,
746 pub profile_id: u16,
747 pub recipient_identity_type: u16,
748 pub flags: u32,
749 pub recipient_identity_length: u32,
750 pub profile_payload_length: u32,
751 pub recipient_identity_digest: [u8; 32],
752 pub recipient_identity_bytes: Vec<u8>,
753 pub profile_payload_bytes: Vec<u8>,
754}
755
756impl RecipientRecordV1 {
757 pub fn with_record_length(mut self, record_length: u32) -> Self {
758 self.record_length = record_length;
759 self
760 }
761
762 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
763 let mut bytes = vec![0u8; KEY_WRAP_RECORD_HEADER_LEN];
764 let computed_identity_length = u32_len(
765 self.recipient_identity_bytes.len(),
766 "RecipientRecordV1 identity length",
767 )?;
768 let computed_profile_payload_length = u32_len(
769 self.profile_payload_bytes.len(),
770 "RecipientRecordV1 profile payload length",
771 )?;
772 let computed_record_length = u32_len(
773 KEY_WRAP_RECORD_HEADER_LEN
774 .checked_add(self.recipient_identity_bytes.len())
775 .and_then(|value| value.checked_add(self.profile_payload_bytes.len()))
776 .ok_or(FormatError::WriterUnsupported(
777 "RecipientRecordV1 length overflow",
778 ))?,
779 "RecipientRecordV1 record length",
780 )?;
781
782 let record_length = if self.record_length == 0 {
783 computed_record_length
784 } else {
785 if self.record_length != computed_record_length {
786 return Err(FormatError::InvalidArchive(
787 "RecipientRecordV1 length is inconsistent with payload sizes",
788 ));
789 }
790 self.record_length
791 };
792 if self.recipient_identity_length != 0
793 && self.recipient_identity_length != computed_identity_length
794 {
795 return Err(FormatError::InvalidArchive(
796 "RecipientRecordV1 identity length is inconsistent with payload size",
797 ));
798 }
799 if self.profile_payload_length != 0
800 && self.profile_payload_length != computed_profile_payload_length
801 {
802 return Err(FormatError::InvalidArchive(
803 "RecipientRecordV1 payload length is inconsistent with payload size",
804 ));
805 }
806
807 let mut digest = Sha256::new();
808 digest.update(&self.recipient_identity_bytes);
809 let mut expected_digest = [0u8; 32];
810 expected_digest.copy_from_slice(&digest.finalize());
811 if self.recipient_identity_digest != [0u8; 32]
812 && self.recipient_identity_digest != expected_digest
813 {
814 return Err(FormatError::InvalidArchive(
815 "RecipientRecordV1 recipient_identity_digest mismatch",
816 ));
817 }
818
819 write_u32(&mut bytes, 0, record_length);
820 write_u16(&mut bytes, 4, self.profile_id);
821 write_u16(&mut bytes, 6, self.recipient_identity_type);
822 write_u32(&mut bytes, 8, 0);
823 write_u32(&mut bytes, 12, computed_identity_length);
824 write_u32(&mut bytes, 16, computed_profile_payload_length);
825 bytes[20..52].copy_from_slice(&expected_digest);
826 bytes.extend_from_slice(&self.recipient_identity_bytes);
827 bytes.extend_from_slice(&self.profile_payload_bytes);
828 Ok(bytes)
829 }
830
831 pub fn parse(bytes: &[u8]) -> Result<(Self, usize), FormatError> {
832 if bytes.len() < KEY_WRAP_RECORD_HEADER_LEN {
833 return Err(FormatError::InvalidLength {
834 structure: "RecipientRecordV1",
835 expected: KEY_WRAP_RECORD_HEADER_LEN,
836 actual: bytes.len(),
837 });
838 }
839
840 let record_length = read_u32(bytes, 0)?;
841 let record_length = usize::try_from(record_length).map_err(|_| {
842 FormatError::InvalidArchive("RecipientRecordV1 record_length overflows memory")
843 })?;
844 if record_length < KEY_WRAP_RECORD_HEADER_LEN {
845 return Err(FormatError::InvalidArchive(
846 "RecipientRecordV1 record_length is too small",
847 ));
848 }
849 if record_length > bytes.len() {
850 return Err(FormatError::InvalidLength {
851 structure: "RecipientRecordV1",
852 expected: record_length,
853 actual: bytes.len(),
854 });
855 }
856
857 let profile_id = read_u16(bytes, 4)?;
858 let recipient_identity_type = read_u16(bytes, 6)?;
859 let flags = read_u32(bytes, 8)?;
860 if flags != 0 {
861 return Err(FormatError::NonZeroReserved {
862 structure: "RecipientRecordV1",
863 });
864 }
865 let recipient_identity_length = read_u32(bytes, 12)?;
866 let profile_payload_length = read_u32(bytes, 16)?;
867 let recipient_identity_length = usize::try_from(recipient_identity_length)
868 .map_err(|_| FormatError::InvalidArchive("RecipientRecordV1 identity length"))?;
869 let profile_payload_length = usize::try_from(profile_payload_length)
870 .map_err(|_| FormatError::InvalidArchive("RecipientRecordV1 payload length"))?;
871 let expected_length = KEY_WRAP_RECORD_HEADER_LEN
872 .checked_add(recipient_identity_length)
873 .and_then(|value| value.checked_add(profile_payload_length))
874 .ok_or(FormatError::InvalidArchive(
875 "RecipientRecordV1 length arithmetic overflow",
876 ))?;
877 if record_length != expected_length {
878 return Err(FormatError::InvalidArchive(
879 "RecipientRecordV1 length is inconsistent with payload sizes",
880 ));
881 }
882
883 let mut recipient_identity_digest = [0u8; 32];
884 recipient_identity_digest.copy_from_slice(&bytes[20..52]);
885 expect_zero("RecipientRecordV1", &bytes[52..68])?;
886
887 let identity_start = KEY_WRAP_RECORD_HEADER_LEN;
888 let identity_end = identity_start + recipient_identity_length;
889 let payload_end = identity_end + profile_payload_length;
890 let recipient_identity_bytes = bytes[identity_start..identity_end].to_vec();
891 let profile_payload_bytes = bytes[identity_end..payload_end].to_vec();
892
893 let mut digest = Sha256::new();
894 digest.update(&recipient_identity_bytes);
895 let mut expected_digest = [0u8; 32];
896 expected_digest.copy_from_slice(&digest.finalize());
897 if recipient_identity_digest != expected_digest {
898 return Err(FormatError::InvalidArchive(
899 "RecipientRecordV1 recipient_identity_digest mismatch",
900 ));
901 }
902
903 Ok((
904 Self {
905 record_length: expected_length as u32,
906 profile_id,
907 recipient_identity_type,
908 flags,
909 recipient_identity_length: recipient_identity_length as u32,
910 profile_payload_length: profile_payload_length as u32,
911 recipient_identity_digest,
912 recipient_identity_bytes,
913 profile_payload_bytes,
914 },
915 record_length,
916 ))
917 }
918}
919
920impl<'a> CryptoHeader<'a> {
921 pub fn parse(bytes: &'a [u8], volume_crypto_header_length: u32) -> Result<Self, FormatError> {
922 let declared_len = volume_crypto_header_length as usize;
923 if volume_crypto_header_length > READER_MAX_CRYPTO_HEADER_LEN {
924 return Err(FormatError::ReaderResourceLimitExceeded {
925 field: "CryptoHeader length",
926 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
927 actual: volume_crypto_header_length as u64,
928 });
929 }
930 if bytes.len() != declared_len {
931 return Err(FormatError::InvalidLength {
932 structure: "CryptoHeader",
933 expected: declared_len,
934 actual: bytes.len(),
935 });
936 }
937 let min_len =
938 CRYPTO_HEADER_FIXED_LEN + 2 + CRYPTO_EXTENSION_HEADER_LEN + CRYPTO_HEADER_HMAC_LEN;
939 if bytes.len() < min_len {
940 return Err(FormatError::CryptoHeaderTooShort {
941 min: min_len,
942 actual: bytes.len(),
943 });
944 }
945
946 let fixed = CryptoHeaderFixed::parse(
947 &bytes[..CRYPTO_HEADER_FIXED_LEN],
948 volume_crypto_header_length,
949 )?;
950 let hmac_offset = bytes.len() - CRYPTO_HEADER_HMAC_LEN;
951 let (kdf_params, kdf_len) =
952 KdfParams::parse(fixed.kdf_algo, &bytes[CRYPTO_HEADER_FIXED_LEN..hmac_offset])?;
953 let extension_bytes = &bytes[CRYPTO_HEADER_FIXED_LEN + kdf_len..hmac_offset];
954 let extensions = scan_crypto_extension_tlvs(extension_bytes)?;
955 let header_hmac = read_array_32(bytes, hmac_offset)?;
956
957 Ok(Self {
958 fixed,
959 kdf_params,
960 extensions,
961 header_hmac,
962 hmac_covered_bytes: &bytes[..hmac_offset],
963 })
964 }
965
966 pub fn validate_extension_semantics(&self) -> Result<(), FormatError> {
967 validate_crypto_extension_semantics(&self.extensions)
968 }
969}
970
971#[derive(Debug, Clone, PartialEq, Eq)]
972pub struct BlockRecord {
973 pub block_index: u64,
974 pub kind: BlockKind,
975 pub flags: u8,
976 pub payload: Vec<u8>,
977 pub record_crc32c: u32,
978}
979
980impl BlockRecord {
981 pub fn parse(bytes: &[u8], block_size: usize) -> Result<Self, FormatError> {
982 let expected = block_size + BLOCK_RECORD_FRAMING_LEN;
983 expect_len("BlockRecord", expected, bytes.len())?;
984 expect_magic("BlockRecord", TZBK_MAGIC, &bytes[0..4])?;
985 expect_zero("BlockRecord", &bytes[14..16])?;
986 expect_crc(
987 "BlockRecord",
988 &bytes[..16 + block_size],
989 read_u32(bytes, 16 + block_size)?,
990 )?;
991
992 let kind = BlockKind::try_from(bytes[12])?;
993 let flags = bytes[13];
994 if flags & BLOCK_RESERVED_FLAGS != 0 {
995 return Err(FormatError::InvalidBlockFlags(flags));
996 }
997 if kind.is_parity() && flags & BLOCK_LAST_DATA_FLAG != 0 {
998 return Err(FormatError::ParityBlockHasLastDataFlag);
999 }
1000
1001 Ok(Self {
1002 block_index: read_u64(bytes, 4)?,
1003 kind,
1004 flags,
1005 payload: bytes[16..16 + block_size].to_vec(),
1006 record_crc32c: read_u32(bytes, 16 + block_size)?,
1007 })
1008 }
1009
1010 pub fn is_last_data(&self) -> bool {
1011 self.flags & BLOCK_LAST_DATA_FLAG != 0
1012 }
1013
1014 pub fn to_bytes(&self) -> Vec<u8> {
1015 Self::to_bytes_from_parts(self.block_index, self.kind, self.flags, &self.payload)
1016 }
1017
1018 pub(crate) fn to_bytes_from_parts(
1019 block_index: u64,
1020 kind: BlockKind,
1021 flags: u8,
1022 payload: &[u8],
1023 ) -> Vec<u8> {
1024 let mut bytes = vec![0u8; payload.len() + BLOCK_RECORD_FRAMING_LEN];
1025 bytes[0..4].copy_from_slice(&TZBK_MAGIC);
1026 write_u64(&mut bytes, 4, block_index);
1027 bytes[12] = kind as u8;
1028 bytes[13] = flags;
1029 bytes[16..16 + payload.len()].copy_from_slice(payload);
1030 let crc = crc32c(&bytes[..16 + payload.len()]);
1031 let crc_offset = 16 + payload.len();
1032 write_u32(&mut bytes, crc_offset, crc);
1033 bytes
1034 }
1035}
1036
1037#[derive(Debug, Clone, PartialEq, Eq)]
1038pub struct ManifestFooter {
1039 pub archive_uuid: [u8; 16],
1040 pub session_id: [u8; 16],
1041 pub volume_index: u32,
1042 pub is_authoritative: u8,
1043 pub total_volumes: u32,
1044 pub index_root_first_block: u64,
1045 pub index_root_data_block_count: u32,
1046 pub index_root_parity_block_count: u32,
1047 pub index_root_encrypted_size: u32,
1048 pub index_root_decompressed_size: u32,
1049 pub manifest_hmac: [u8; 32],
1050}
1051
1052impl ManifestFooter {
1053 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1054 expect_len("ManifestFooter", MANIFEST_FOOTER_LEN, bytes.len())?;
1055 expect_magic("ManifestFooter", TZMF_MAGIC, &bytes[0..4])?;
1056 expect_zero("ManifestFooter", &bytes[41..44])?;
1057 expect_zero("ManifestFooter", &bytes[72..104])?;
1058 let is_authoritative = bytes[40];
1059 if is_authoritative > 1 {
1060 return Err(FormatError::InvalidAuthoritativeFlag(is_authoritative));
1061 }
1062
1063 Ok(Self {
1064 archive_uuid: read_array_16(bytes, 4)?,
1065 session_id: read_array_16(bytes, 20)?,
1066 volume_index: read_u32(bytes, 36)?,
1067 is_authoritative,
1068 total_volumes: read_u32(bytes, 44)?,
1069 index_root_first_block: read_u64(bytes, 48)?,
1070 index_root_data_block_count: read_u32(bytes, 56)?,
1071 index_root_parity_block_count: read_u32(bytes, 60)?,
1072 index_root_encrypted_size: read_u32(bytes, 64)?,
1073 index_root_decompressed_size: read_u32(bytes, 68)?,
1074 manifest_hmac: read_array_32(bytes, 104)?,
1075 })
1076 }
1077
1078 pub fn validate_index_root_extent(&self, block_size: u32) -> Result<(), FormatError> {
1079 if self.index_root_data_block_count == 0 || self.index_root_encrypted_size == 0 {
1080 return Err(FormatError::EmptyIndexRootExtent);
1081 }
1082 let expected = self
1083 .index_root_data_block_count
1084 .checked_mul(block_size)
1085 .ok_or(FormatError::IndexRootSizeMismatch)?;
1086 if expected != self.index_root_encrypted_size {
1087 return Err(FormatError::IndexRootSizeMismatch);
1088 }
1089 Ok(())
1090 }
1091
1092 pub fn to_bytes(&self) -> [u8; MANIFEST_FOOTER_LEN] {
1093 let mut bytes = [0u8; MANIFEST_FOOTER_LEN];
1094 bytes[0..4].copy_from_slice(&TZMF_MAGIC);
1095 bytes[4..20].copy_from_slice(&self.archive_uuid);
1096 bytes[20..36].copy_from_slice(&self.session_id);
1097 write_u32(&mut bytes, 36, self.volume_index);
1098 bytes[40] = self.is_authoritative;
1099 write_u32(&mut bytes, 44, self.total_volumes);
1100 write_u64(&mut bytes, 48, self.index_root_first_block);
1101 write_u32(&mut bytes, 56, self.index_root_data_block_count);
1102 write_u32(&mut bytes, 60, self.index_root_parity_block_count);
1103 write_u32(&mut bytes, 64, self.index_root_encrypted_size);
1104 write_u32(&mut bytes, 68, self.index_root_decompressed_size);
1105 bytes[104..136].copy_from_slice(&self.manifest_hmac);
1106 bytes
1107 }
1108}
1109
1110#[derive(Debug, Clone, PartialEq, Eq)]
1111pub struct VolumeTrailer {
1112 pub archive_uuid: [u8; 16],
1113 pub session_id: [u8; 16],
1114 pub volume_index: u32,
1115 pub block_count: u64,
1116 pub bytes_written: u64,
1117 pub manifest_footer_offset: u64,
1118 pub manifest_footer_length: u32,
1119 pub closed_at_ns: i64,
1120 pub root_auth_footer_offset: u64,
1121 pub root_auth_footer_length: u32,
1122 pub root_auth_flags: u32,
1123 pub trailer_hmac: [u8; 32],
1124}
1125
1126impl VolumeTrailer {
1127 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1128 expect_len("VolumeTrailer", VOLUME_TRAILER_LEN, bytes.len())?;
1129 expect_magic("VolumeTrailer", TZVT_MAGIC, &bytes[0..4])?;
1130 expect_zero("VolumeTrailer", &bytes[92..96])?;
1131 let manifest_footer_length = read_u32(bytes, 64)?;
1132 if manifest_footer_length != MANIFEST_FOOTER_LEN as u32 {
1133 return Err(FormatError::InvalidManifestFooterLength(
1134 manifest_footer_length,
1135 ));
1136 }
1137 let root_auth_flags = read_u32(bytes, 88)?;
1138 if root_auth_flags & !0x0000_0001 != 0 {
1139 return Err(FormatError::InvalidArchive(
1140 "VolumeTrailer root_auth_flags has unknown bits",
1141 ));
1142 }
1143
1144 Ok(Self {
1145 archive_uuid: read_array_16(bytes, 4)?,
1146 session_id: read_array_16(bytes, 20)?,
1147 volume_index: read_u32(bytes, 36)?,
1148 block_count: read_u64(bytes, 40)?,
1149 bytes_written: read_u64(bytes, 48)?,
1150 manifest_footer_offset: read_u64(bytes, 56)?,
1151 manifest_footer_length,
1152 closed_at_ns: read_i64(bytes, 68)?,
1153 root_auth_footer_offset: read_u64(bytes, 76)?,
1154 root_auth_footer_length: read_u32(bytes, 84)?,
1155 root_auth_flags,
1156 trailer_hmac: read_array_32(bytes, 96)?,
1157 })
1158 }
1159
1160 pub fn to_bytes(&self) -> [u8; VOLUME_TRAILER_LEN] {
1161 let mut bytes = [0u8; VOLUME_TRAILER_LEN];
1162 bytes[0..4].copy_from_slice(&TZVT_MAGIC);
1163 bytes[4..20].copy_from_slice(&self.archive_uuid);
1164 bytes[20..36].copy_from_slice(&self.session_id);
1165 write_u32(&mut bytes, 36, self.volume_index);
1166 write_u64(&mut bytes, 40, self.block_count);
1167 write_u64(&mut bytes, 48, self.bytes_written);
1168 write_u64(&mut bytes, 56, self.manifest_footer_offset);
1169 write_u32(&mut bytes, 64, self.manifest_footer_length);
1170 write_i64(&mut bytes, 68, self.closed_at_ns);
1171 write_u64(&mut bytes, 76, self.root_auth_footer_offset);
1172 write_u32(&mut bytes, 84, self.root_auth_footer_length);
1173 write_u32(&mut bytes, 88, self.root_auth_flags);
1174 bytes[96..128].copy_from_slice(&self.trailer_hmac);
1175 bytes
1176 }
1177}
1178
1179#[derive(Debug, Clone, PartialEq, Eq)]
1180pub struct RootAuthFooterV1 {
1181 pub archive_uuid: [u8; 16],
1182 pub session_id: [u8; 16],
1183 pub format_version: u16,
1184 pub volume_format_rev: u16,
1185 pub authenticator_id: u16,
1186 pub signer_identity_type: u16,
1187 pub signer_identity_bytes: Vec<u8>,
1188 pub authenticator_value: Vec<u8>,
1189 pub total_data_block_count: u64,
1190 pub critical_metadata_digest: [u8; 32],
1191 pub index_digest: [u8; 32],
1192 pub fec_layout_digest: [u8; 32],
1193 pub data_block_merkle_root: [u8; 32],
1194 pub signer_identity_digest: [u8; 32],
1195 pub archive_root: [u8; 32],
1196 pub footer_crc32c: u32,
1197}
1198
1199impl RootAuthFooterV1 {
1200 pub fn footer_length(&self) -> Result<u32, FormatError> {
1201 root_auth_footer_length(
1202 self.signer_identity_bytes.len(),
1203 self.authenticator_value.len(),
1204 )
1205 }
1206
1207 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
1208 validate_root_auth_variable_lengths(
1209 self.signer_identity_bytes.len(),
1210 self.authenticator_value.len(),
1211 )?;
1212 let footer_length = self.footer_length()?;
1213 let root_auth_spec_id =
1214 root_auth_spec_id_for_revision(self.format_version, self.volume_format_rev)?;
1215 let mut bytes = vec![0u8; footer_length as usize];
1216 bytes[0..4].copy_from_slice(&TZRA_MAGIC);
1217 write_u16(&mut bytes, 4, 1);
1218 bytes[6..30].copy_from_slice(&root_auth_spec_id);
1219 write_u32(&mut bytes, 30, footer_length);
1220 write_u32(&mut bytes, 34, 0);
1221 bytes[38..54].copy_from_slice(&self.archive_uuid);
1222 bytes[54..70].copy_from_slice(&self.session_id);
1223 write_u16(&mut bytes, 70, self.format_version);
1224 write_u16(&mut bytes, 72, self.volume_format_rev);
1225 write_u16(&mut bytes, 74, self.authenticator_id);
1226 write_u16(&mut bytes, 76, self.signer_identity_type);
1227 write_u32(
1228 &mut bytes,
1229 78,
1230 u32::try_from(self.signer_identity_bytes.len()).map_err(|_| {
1231 FormatError::InvalidArchive("RootAuthFooterV1 signer identity length overflow")
1232 })?,
1233 );
1234 write_u32(
1235 &mut bytes,
1236 82,
1237 u32::try_from(self.authenticator_value.len()).map_err(|_| {
1238 FormatError::InvalidArchive("RootAuthFooterV1 authenticator length overflow")
1239 })?,
1240 );
1241 write_u64(&mut bytes, 86, self.total_data_block_count);
1242 bytes[94..126].copy_from_slice(&self.critical_metadata_digest);
1243 bytes[126..158].copy_from_slice(&self.index_digest);
1244 bytes[158..190].copy_from_slice(&self.fec_layout_digest);
1245 bytes[190..222].copy_from_slice(&self.data_block_merkle_root);
1246 bytes[222..254].copy_from_slice(&self.signer_identity_digest);
1247 bytes[254..286].copy_from_slice(&self.archive_root);
1248 let signer_start = ROOT_AUTH_FOOTER_FIXED_LEN;
1249 let signer_end = signer_start + self.signer_identity_bytes.len();
1250 bytes[signer_start..signer_end].copy_from_slice(&self.signer_identity_bytes);
1251 let auth_end = signer_end + self.authenticator_value.len();
1252 bytes[signer_end..auth_end].copy_from_slice(&self.authenticator_value);
1253 let crc = crc32c(&bytes[..auth_end]);
1254 write_u32(&mut bytes, auth_end, crc);
1255 Ok(bytes)
1256 }
1257
1258 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1259 if bytes.len() > READER_MAX_ROOT_AUTH_FOOTER_LEN as usize {
1260 return Err(FormatError::ReaderResourceLimitExceeded {
1261 field: "RootAuthFooterV1 length",
1262 cap: READER_MAX_ROOT_AUTH_FOOTER_LEN as u64,
1263 actual: bytes.len() as u64,
1264 });
1265 }
1266 let min_len = ROOT_AUTH_FOOTER_FIXED_LEN + 4;
1267 if bytes.len() < min_len {
1268 return Err(FormatError::InvalidLength {
1269 structure: "RootAuthFooterV1",
1270 expected: min_len,
1271 actual: bytes.len(),
1272 });
1273 }
1274 expect_magic("RootAuthFooterV1", TZRA_MAGIC, &bytes[0..4])?;
1275 let version = read_u16(bytes, 4)?;
1276 if version != 1 {
1277 return Err(FormatError::UnsupportedFormatVersion(version));
1278 }
1279 let root_auth_spec_id = read_array_24(bytes, 6)?;
1280 let footer_length = read_u32(bytes, 30)?;
1281 if footer_length as usize != bytes.len() {
1282 return Err(FormatError::InvalidLength {
1283 structure: "RootAuthFooterV1",
1284 expected: footer_length as usize,
1285 actual: bytes.len(),
1286 });
1287 }
1288 if read_u32(bytes, 34)? != 0 {
1289 return Err(FormatError::InvalidArchive(
1290 "RootAuthFooterV1 flags must be zero",
1291 ));
1292 }
1293 let format_version = read_u16(bytes, 70)?;
1294 if format_version != FORMAT_VERSION {
1295 return Err(FormatError::UnsupportedFormatVersion(format_version));
1296 }
1297 let volume_format_rev = read_u16(bytes, 72)?;
1298 if root_auth_spec_id != root_auth_spec_id_for_revision(format_version, volume_format_rev)? {
1299 return Err(FormatError::InvalidArchive(
1300 "RootAuthFooterV1 root_auth_spec_id does not match volume_format_rev",
1301 ));
1302 }
1303 let signer_identity_length = read_u32(bytes, 78)?;
1304 let authenticator_value_length = read_u32(bytes, 82)?;
1305 validate_root_auth_variable_lengths(
1306 signer_identity_length as usize,
1307 authenticator_value_length as usize,
1308 )?;
1309 let expected = root_auth_footer_length(
1310 signer_identity_length as usize,
1311 authenticator_value_length as usize,
1312 )?;
1313 if expected != footer_length {
1314 return Err(FormatError::InvalidLength {
1315 structure: "RootAuthFooterV1",
1316 expected: expected as usize,
1317 actual: footer_length as usize,
1318 });
1319 }
1320 expect_zero("RootAuthFooterV1", &bytes[286..318])?;
1321 let crc_offset = bytes.len() - 4;
1322 expect_crc(
1323 "RootAuthFooterV1",
1324 &bytes[..crc_offset],
1325 read_u32(bytes, crc_offset)?,
1326 )?;
1327
1328 let signer_start = ROOT_AUTH_FOOTER_FIXED_LEN;
1329 let signer_end = signer_start + signer_identity_length as usize;
1330 let auth_end = signer_end + authenticator_value_length as usize;
1331 Ok(Self {
1332 archive_uuid: read_array_16(bytes, 38)?,
1333 session_id: read_array_16(bytes, 54)?,
1334 format_version,
1335 volume_format_rev,
1336 authenticator_id: read_u16(bytes, 74)?,
1337 signer_identity_type: read_u16(bytes, 76)?,
1338 signer_identity_bytes: bytes[signer_start..signer_end].to_vec(),
1339 authenticator_value: bytes[signer_end..auth_end].to_vec(),
1340 total_data_block_count: read_u64(bytes, 86)?,
1341 critical_metadata_digest: read_array_32(bytes, 94)?,
1342 index_digest: read_array_32(bytes, 126)?,
1343 fec_layout_digest: read_array_32(bytes, 158)?,
1344 data_block_merkle_root: read_array_32(bytes, 190)?,
1345 signer_identity_digest: read_array_32(bytes, 222)?,
1346 archive_root: read_array_32(bytes, 254)?,
1347 footer_crc32c: read_u32(bytes, crc_offset)?,
1348 })
1349 }
1350}
1351
1352fn root_auth_footer_length(
1353 signer_identity_len: usize,
1354 authenticator_value_len: usize,
1355) -> Result<u32, FormatError> {
1356 let len = ROOT_AUTH_FOOTER_FIXED_LEN
1357 .checked_add(signer_identity_len)
1358 .and_then(|value| value.checked_add(authenticator_value_len))
1359 .and_then(|value| value.checked_add(4))
1360 .ok_or(FormatError::InvalidArchive(
1361 "RootAuthFooterV1 length overflow",
1362 ))?;
1363 if len > READER_MAX_ROOT_AUTH_FOOTER_LEN as usize {
1364 return Err(FormatError::ReaderResourceLimitExceeded {
1365 field: "RootAuthFooterV1 length",
1366 cap: READER_MAX_ROOT_AUTH_FOOTER_LEN as u64,
1367 actual: len as u64,
1368 });
1369 }
1370 u32::try_from(len).map_err(|_| FormatError::InvalidArchive("RootAuthFooterV1 length overflow"))
1371}
1372
1373fn validate_root_auth_variable_lengths(
1374 signer_identity_len: usize,
1375 authenticator_value_len: usize,
1376) -> Result<(), FormatError> {
1377 if signer_identity_len > READER_MAX_ROOT_AUTH_SIGNER_IDENTITY_LEN as usize {
1378 return Err(FormatError::ReaderResourceLimitExceeded {
1379 field: "RootAuthFooterV1 signer identity length",
1380 cap: READER_MAX_ROOT_AUTH_SIGNER_IDENTITY_LEN as u64,
1381 actual: signer_identity_len as u64,
1382 });
1383 }
1384 if authenticator_value_len > READER_MAX_ROOT_AUTH_AUTHENTICATOR_VALUE_LEN as usize {
1385 return Err(FormatError::ReaderResourceLimitExceeded {
1386 field: "RootAuthFooterV1 authenticator value length",
1387 cap: READER_MAX_ROOT_AUTH_AUTHENTICATOR_VALUE_LEN as u64,
1388 actual: authenticator_value_len as u64,
1389 });
1390 }
1391 Ok(())
1392}
1393
1394#[derive(Debug, Clone, PartialEq, Eq)]
1395pub struct SerializedRegion {
1396 pub region_type: u16,
1397 pub offset: u64,
1398 pub bytes: Vec<u8>,
1399}
1400
1401impl SerializedRegion {
1402 pub fn encoded_len(&self) -> usize {
1403 SERIALIZED_REGION_HEADER_LEN + self.bytes.len()
1404 }
1405
1406 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
1407 let length = u32::try_from(self.bytes.len())
1408 .map_err(|_| FormatError::InvalidArchive("SerializedRegion length exceeds u32"))?;
1409 let mut bytes = vec![0u8; self.encoded_len()];
1410 write_u16(&mut bytes, 0, self.region_type);
1411 write_u64(&mut bytes, 4, self.offset);
1412 write_u32(&mut bytes, 12, length);
1413 bytes[SERIALIZED_REGION_HEADER_LEN..].copy_from_slice(&self.bytes);
1414 Ok(bytes)
1415 }
1416}
1417
1418#[derive(Debug, Clone, PartialEq, Eq)]
1419pub struct CriticalMetadataImage {
1420 pub volume_format_rev: u16,
1421 pub archive_uuid: [u8; 16],
1422 pub session_id: [u8; 16],
1423 pub volume_index: u32,
1424 pub stripe_width: u32,
1425 pub layout_flags: u32,
1426 pub volume_header_offset: u64,
1427 pub volume_header_length: u32,
1428 pub crypto_header_offset: u64,
1429 pub crypto_header_length: u32,
1430 pub key_wrap_table_offset: u64,
1431 pub key_wrap_table_length: u32,
1432 pub block_records_offset: u64,
1433 pub block_records_length: u64,
1434 pub block_count: u64,
1435 pub manifest_footer_offset: u64,
1436 pub manifest_footer_length: u32,
1437 pub root_auth_footer_offset: u64,
1438 pub root_auth_footer_length: u32,
1439 pub volume_trailer_offset: u64,
1440 pub volume_trailer_length: u32,
1441 pub body_bytes_before_cmra: u64,
1442 pub volume_header_sha256: [u8; 32],
1443 pub crypto_header_sha256: [u8; 32],
1444 pub key_wrap_table_sha256: [u8; 32],
1445 pub manifest_footer_sha256: [u8; 32],
1446 pub root_auth_footer_sha256: [u8; 32],
1447 pub volume_trailer_sha256: [u8; 32],
1448 pub regions: Vec<SerializedRegion>,
1449}
1450
1451fn critical_metadata_image_fixed_len(volume_format_rev: u16) -> Result<usize, FormatError> {
1452 match volume_format_rev {
1453 VOLUME_FORMAT_REV_43 => Ok(CRITICAL_METADATA_IMAGE_FIXED_LEN_V43),
1454 VOLUME_FORMAT_REV_44 => Ok(CRITICAL_METADATA_IMAGE_FIXED_LEN),
1455 _ => Err(FormatError::UnsupportedVolumeFormatRevision {
1456 format_version: FORMAT_VERSION,
1457 volume_format_rev,
1458 reader_max_supported_revision: READER_MAX_SUPPORTED_VOLUME_FORMAT_REV,
1459 }),
1460 }
1461}
1462
1463impl CriticalMetadataImage {
1464 pub fn to_bytes(&self) -> Result<Vec<u8>, FormatError> {
1465 let fixed_len = critical_metadata_image_fixed_len(self.volume_format_rev)?;
1466 if self.volume_format_rev == VOLUME_FORMAT_REV_43
1467 && (self.layout_flags & !0x0000_0001 != 0
1468 || self.key_wrap_table_offset != 0
1469 || self.key_wrap_table_length != 0
1470 || self.key_wrap_table_sha256 != [0u8; 32]
1471 || self.regions.iter().any(|region| region.region_type == 6))
1472 {
1473 return Err(FormatError::InvalidArchive(
1474 "v43 CriticalMetadataImage cannot contain KeyWrapTableV1",
1475 ));
1476 }
1477 let region_count = u16::try_from(self.regions.len()).map_err(|_| {
1478 FormatError::InvalidArchive("CriticalMetadataImage has too many regions")
1479 })?;
1480 let variable_len = self.regions.iter().try_fold(0usize, |total, region| {
1481 total
1482 .checked_add(region.encoded_len())
1483 .ok_or(FormatError::InvalidArchive(
1484 "CriticalMetadataImage length overflow",
1485 ))
1486 })?;
1487 let mut bytes = vec![
1488 0u8;
1489 fixed_len
1490 .checked_add(variable_len)
1491 .and_then(|value| value.checked_add(IMAGE_CRC_LEN))
1492 .ok_or(FormatError::InvalidArchive(
1493 "CriticalMetadataImage length overflow",
1494 ))?
1495 ];
1496 bytes[0..4].copy_from_slice(&TZMI_MAGIC);
1497 write_u16(&mut bytes, 4, 1);
1498 write_u16(&mut bytes, 6, self.volume_format_rev);
1499 bytes[8..24].copy_from_slice(&self.archive_uuid);
1500 bytes[24..40].copy_from_slice(&self.session_id);
1501 write_u32(&mut bytes, 40, self.volume_index);
1502 write_u32(&mut bytes, 44, self.stripe_width);
1503 write_u32(&mut bytes, 48, self.layout_flags);
1504 write_u64(&mut bytes, 52, self.volume_header_offset);
1505 write_u32(&mut bytes, 60, self.volume_header_length);
1506 write_u64(&mut bytes, 64, self.crypto_header_offset);
1507 write_u32(&mut bytes, 72, self.crypto_header_length);
1508 if self.volume_format_rev == VOLUME_FORMAT_REV_44 {
1509 write_u64(&mut bytes, 76, self.key_wrap_table_offset);
1510 write_u32(&mut bytes, 84, self.key_wrap_table_length);
1511 write_u64(&mut bytes, 88, self.block_records_offset);
1512 write_u64(&mut bytes, 96, self.block_records_length);
1513 write_u64(&mut bytes, 104, self.block_count);
1514 write_u64(&mut bytes, 112, self.manifest_footer_offset);
1515 write_u32(&mut bytes, 120, self.manifest_footer_length);
1516 write_u64(&mut bytes, 124, self.root_auth_footer_offset);
1517 write_u32(&mut bytes, 132, self.root_auth_footer_length);
1518 write_u64(&mut bytes, 136, self.volume_trailer_offset);
1519 write_u32(&mut bytes, 144, self.volume_trailer_length);
1520 write_u64(&mut bytes, 148, self.body_bytes_before_cmra);
1521 bytes[156..188].copy_from_slice(&self.volume_header_sha256);
1522 bytes[188..220].copy_from_slice(&self.crypto_header_sha256);
1523 bytes[220..252].copy_from_slice(&self.key_wrap_table_sha256);
1524 bytes[252..284].copy_from_slice(&self.manifest_footer_sha256);
1525 bytes[284..316].copy_from_slice(&self.root_auth_footer_sha256);
1526 bytes[316..348].copy_from_slice(&self.volume_trailer_sha256);
1527 write_u16(&mut bytes, 348, region_count);
1528 } else {
1529 write_u64(&mut bytes, 76, self.block_records_offset);
1530 write_u64(&mut bytes, 84, self.block_records_length);
1531 write_u64(&mut bytes, 92, self.block_count);
1532 write_u64(&mut bytes, 100, self.manifest_footer_offset);
1533 write_u32(&mut bytes, 108, self.manifest_footer_length);
1534 write_u64(&mut bytes, 112, self.root_auth_footer_offset);
1535 write_u32(&mut bytes, 120, self.root_auth_footer_length);
1536 write_u64(&mut bytes, 124, self.volume_trailer_offset);
1537 write_u32(&mut bytes, 132, self.volume_trailer_length);
1538 write_u64(&mut bytes, 136, self.body_bytes_before_cmra);
1539 bytes[144..176].copy_from_slice(&self.volume_header_sha256);
1540 bytes[176..208].copy_from_slice(&self.crypto_header_sha256);
1541 bytes[208..240].copy_from_slice(&self.manifest_footer_sha256);
1542 bytes[240..272].copy_from_slice(&self.root_auth_footer_sha256);
1543 bytes[272..304].copy_from_slice(&self.volume_trailer_sha256);
1544 write_u16(&mut bytes, 304, region_count);
1545 }
1546
1547 let mut cursor = fixed_len;
1548 for region in &self.regions {
1549 let region_bytes = region.to_bytes()?;
1550 let end = cursor + region_bytes.len();
1551 bytes[cursor..end].copy_from_slice(®ion_bytes);
1552 cursor = end;
1553 }
1554 let crc = crc32c(&bytes[..cursor]);
1555 write_u32(&mut bytes, cursor, crc);
1556 Ok(bytes)
1557 }
1558
1559 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1560 if bytes.len() < 8 + IMAGE_CRC_LEN {
1561 return Err(FormatError::InvalidLength {
1562 structure: "CriticalMetadataImageV1",
1563 expected: 8 + IMAGE_CRC_LEN,
1564 actual: bytes.len(),
1565 });
1566 }
1567 expect_magic("CriticalMetadataImageV1", TZMI_MAGIC, &bytes[0..4])?;
1568 let version = read_u16(bytes, 4)?;
1569 if version != 1 {
1570 return Err(FormatError::UnsupportedFormatVersion(version));
1571 }
1572 let volume_format_rev = read_u16(bytes, 6)?;
1573 let fixed_len = critical_metadata_image_fixed_len(volume_format_rev)?;
1574 if bytes.len() < fixed_len + IMAGE_CRC_LEN {
1575 return Err(FormatError::InvalidLength {
1576 structure: "CriticalMetadataImageV1",
1577 expected: fixed_len + IMAGE_CRC_LEN,
1578 actual: bytes.len(),
1579 });
1580 }
1581 if volume_format_rev > READER_MAX_SUPPORTED_VOLUME_FORMAT_REV {
1582 return Err(FormatError::UnsupportedVolumeFormatRevision {
1583 format_version: FORMAT_VERSION,
1584 volume_format_rev,
1585 reader_max_supported_revision: READER_MAX_SUPPORTED_VOLUME_FORMAT_REV,
1586 });
1587 }
1588 let layout_flags = read_u32(bytes, 48)?;
1589 let allowed_layout_flags = if volume_format_rev == VOLUME_FORMAT_REV_44 {
1590 0x0000_0003
1591 } else {
1592 0x0000_0001
1593 };
1594 if layout_flags & !allowed_layout_flags != 0 {
1595 return Err(FormatError::InvalidArchive(
1596 "CriticalMetadataImage layout_flags has unknown bits",
1597 ));
1598 }
1599 if volume_format_rev == VOLUME_FORMAT_REV_44 {
1600 expect_zero("CriticalMetadataImageV1", &bytes[350..364])?;
1601 } else {
1602 expect_zero("CriticalMetadataImageV1", &bytes[306..320])?;
1603 }
1604 let expected_crc_offset =
1605 bytes
1606 .len()
1607 .checked_sub(IMAGE_CRC_LEN)
1608 .ok_or(FormatError::InvalidArchive(
1609 "CriticalMetadataImage length underflow",
1610 ))?;
1611 expect_crc(
1612 "CriticalMetadataImageV1",
1613 &bytes[..expected_crc_offset],
1614 read_u32(bytes, expected_crc_offset)?,
1615 )?;
1616
1617 let serialized_region_count = if volume_format_rev == VOLUME_FORMAT_REV_44 {
1618 read_u16(bytes, 348)?
1619 } else {
1620 read_u16(bytes, 304)?
1621 } as usize;
1622 let mut cursor = fixed_len;
1623 let mut regions = Vec::with_capacity(serialized_region_count);
1624 for _ in 0..serialized_region_count {
1625 if cursor + SERIALIZED_REGION_HEADER_LEN > expected_crc_offset {
1626 return Err(FormatError::InvalidLength {
1627 structure: "SerializedRegion",
1628 expected: cursor + SERIALIZED_REGION_HEADER_LEN,
1629 actual: bytes.len(),
1630 });
1631 }
1632 let region_type = read_u16(bytes, cursor)?;
1633 if read_u16(bytes, cursor + 2)? != 0 {
1634 return Err(FormatError::NonZeroReserved {
1635 structure: "SerializedRegion",
1636 });
1637 }
1638 let offset = read_u64(bytes, cursor + 4)?;
1639 let length = read_u32(bytes, cursor + 12)? as usize;
1640 cursor += SERIALIZED_REGION_HEADER_LEN;
1641 let end = cursor
1642 .checked_add(length)
1643 .ok_or(FormatError::InvalidArchive(
1644 "SerializedRegion length overflow",
1645 ))?;
1646 if end > expected_crc_offset {
1647 return Err(FormatError::InvalidLength {
1648 structure: "SerializedRegion",
1649 expected: end,
1650 actual: bytes.len(),
1651 });
1652 }
1653 regions.push(SerializedRegion {
1654 region_type,
1655 offset,
1656 bytes: bytes[cursor..end].to_vec(),
1657 });
1658 cursor = end;
1659 }
1660 if cursor != expected_crc_offset {
1661 return Err(FormatError::InvalidArchive(
1662 "CriticalMetadataImage has trailing region bytes",
1663 ));
1664 }
1665
1666 if volume_format_rev == VOLUME_FORMAT_REV_44 {
1667 Ok(Self {
1668 volume_format_rev,
1669 archive_uuid: read_array_16(bytes, 8)?,
1670 session_id: read_array_16(bytes, 24)?,
1671 volume_index: read_u32(bytes, 40)?,
1672 stripe_width: read_u32(bytes, 44)?,
1673 layout_flags,
1674 volume_header_offset: read_u64(bytes, 52)?,
1675 volume_header_length: read_u32(bytes, 60)?,
1676 crypto_header_offset: read_u64(bytes, 64)?,
1677 crypto_header_length: read_u32(bytes, 72)?,
1678 key_wrap_table_offset: read_u64(bytes, 76)?,
1679 key_wrap_table_length: read_u32(bytes, 84)?,
1680 block_records_offset: read_u64(bytes, 88)?,
1681 block_records_length: read_u64(bytes, 96)?,
1682 block_count: read_u64(bytes, 104)?,
1683 manifest_footer_offset: read_u64(bytes, 112)?,
1684 manifest_footer_length: read_u32(bytes, 120)?,
1685 root_auth_footer_offset: read_u64(bytes, 124)?,
1686 root_auth_footer_length: read_u32(bytes, 132)?,
1687 volume_trailer_offset: read_u64(bytes, 136)?,
1688 volume_trailer_length: read_u32(bytes, 144)?,
1689 body_bytes_before_cmra: read_u64(bytes, 148)?,
1690 volume_header_sha256: read_array_32(bytes, 156)?,
1691 crypto_header_sha256: read_array_32(bytes, 188)?,
1692 key_wrap_table_sha256: read_array_32(bytes, 220)?,
1693 manifest_footer_sha256: read_array_32(bytes, 252)?,
1694 root_auth_footer_sha256: read_array_32(bytes, 284)?,
1695 volume_trailer_sha256: read_array_32(bytes, 316)?,
1696 regions,
1697 })
1698 } else {
1699 Ok(Self {
1700 volume_format_rev,
1701 archive_uuid: read_array_16(bytes, 8)?,
1702 session_id: read_array_16(bytes, 24)?,
1703 volume_index: read_u32(bytes, 40)?,
1704 stripe_width: read_u32(bytes, 44)?,
1705 layout_flags,
1706 volume_header_offset: read_u64(bytes, 52)?,
1707 volume_header_length: read_u32(bytes, 60)?,
1708 crypto_header_offset: read_u64(bytes, 64)?,
1709 crypto_header_length: read_u32(bytes, 72)?,
1710 key_wrap_table_offset: 0,
1711 key_wrap_table_length: 0,
1712 block_records_offset: read_u64(bytes, 76)?,
1713 block_records_length: read_u64(bytes, 84)?,
1714 block_count: read_u64(bytes, 92)?,
1715 manifest_footer_offset: read_u64(bytes, 100)?,
1716 manifest_footer_length: read_u32(bytes, 108)?,
1717 root_auth_footer_offset: read_u64(bytes, 112)?,
1718 root_auth_footer_length: read_u32(bytes, 120)?,
1719 volume_trailer_offset: read_u64(bytes, 124)?,
1720 volume_trailer_length: read_u32(bytes, 132)?,
1721 body_bytes_before_cmra: read_u64(bytes, 136)?,
1722 volume_header_sha256: read_array_32(bytes, 144)?,
1723 crypto_header_sha256: read_array_32(bytes, 176)?,
1724 key_wrap_table_sha256: [0u8; 32],
1725 manifest_footer_sha256: read_array_32(bytes, 208)?,
1726 root_auth_footer_sha256: read_array_32(bytes, 240)?,
1727 volume_trailer_sha256: read_array_32(bytes, 272)?,
1728 regions,
1729 })
1730 }
1731 }
1732
1733 pub fn region(&self, region_type: u16) -> Option<&SerializedRegion> {
1734 self.regions
1735 .iter()
1736 .find(|region| region.region_type == region_type)
1737 }
1738}
1739
1740#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1741pub struct CriticalMetadataRecoveryHeader {
1742 pub shard_size: u32,
1743 pub data_shard_count: u16,
1744 pub parity_shard_count: u16,
1745 pub image_length: u32,
1746 pub archive_uuid_hint: [u8; 16],
1747 pub session_id_hint: [u8; 16],
1748 pub volume_index_hint: u32,
1749 pub image_sha256: [u8; 32],
1750 pub header_crc32c: u32,
1751}
1752
1753impl CriticalMetadataRecoveryHeader {
1754 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1755 expect_len(
1756 "CriticalMetadataRecoveryHeader",
1757 CRITICAL_METADATA_RECOVERY_HEADER_LEN,
1758 bytes.len(),
1759 )?;
1760 expect_magic("CriticalMetadataRecoveryHeader", TZCR_MAGIC, &bytes[0..4])?;
1761 let version = read_u16(bytes, 4)?;
1762 if version != 1 {
1763 return Err(FormatError::UnsupportedFormatVersion(version));
1764 }
1765 let fec_algo = read_u16(bytes, 6)?;
1766 if fec_algo != FecAlgo::ReedSolomonGF16 as u16 {
1767 return Err(FormatError::UnknownFecAlgo(fec_algo));
1768 }
1769 expect_zero("CriticalMetadataRecoveryHeader", &bytes[88..112])?;
1770 expect_crc(
1771 "CriticalMetadataRecoveryHeader",
1772 &bytes[..112],
1773 read_u32(bytes, 112)?,
1774 )?;
1775 Ok(Self {
1776 shard_size: read_u32(bytes, 8)?,
1777 data_shard_count: read_u16(bytes, 12)?,
1778 parity_shard_count: read_u16(bytes, 14)?,
1779 image_length: read_u32(bytes, 16)?,
1780 archive_uuid_hint: read_array_16(bytes, 20)?,
1781 session_id_hint: read_array_16(bytes, 36)?,
1782 volume_index_hint: read_u32(bytes, 52)?,
1783 image_sha256: read_array_32(bytes, 56)?,
1784 header_crc32c: read_u32(bytes, 112)?,
1785 })
1786 }
1787
1788 pub fn to_bytes(&self) -> [u8; CRITICAL_METADATA_RECOVERY_HEADER_LEN] {
1789 let mut bytes = [0u8; CRITICAL_METADATA_RECOVERY_HEADER_LEN];
1790 bytes[0..4].copy_from_slice(&TZCR_MAGIC);
1791 write_u16(&mut bytes, 4, 1);
1792 write_u16(&mut bytes, 6, FecAlgo::ReedSolomonGF16 as u16);
1793 write_u32(&mut bytes, 8, self.shard_size);
1794 write_u16(&mut bytes, 12, self.data_shard_count);
1795 write_u16(&mut bytes, 14, self.parity_shard_count);
1796 write_u32(&mut bytes, 16, self.image_length);
1797 bytes[20..36].copy_from_slice(&self.archive_uuid_hint);
1798 bytes[36..52].copy_from_slice(&self.session_id_hint);
1799 write_u32(&mut bytes, 52, self.volume_index_hint);
1800 bytes[56..88].copy_from_slice(&self.image_sha256);
1801 let crc = crc32c(&bytes[..112]);
1802 write_u32(&mut bytes, 112, crc);
1803 bytes
1804 }
1805}
1806
1807#[derive(Debug, Clone, PartialEq, Eq)]
1808pub struct CriticalMetadataRecoveryShard {
1809 pub shard_index: u16,
1810 pub shard_role: u8,
1811 pub shard_payload_length: u32,
1812 pub payload: Vec<u8>,
1813 pub shard_crc32c: u32,
1814}
1815
1816impl CriticalMetadataRecoveryShard {
1817 pub fn parse(bytes: &[u8], shard_size: usize) -> Result<Self, FormatError> {
1818 let expected = CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN
1819 .checked_add(shard_size)
1820 .ok_or(FormatError::InvalidArchive("CMRA shard length overflow"))?;
1821 expect_len("CriticalMetadataRecoveryShard", expected, bytes.len())?;
1822 expect_magic("CriticalMetadataRecoveryShard", TZCS_MAGIC, &bytes[0..4])?;
1823 if bytes[7] != 0 {
1824 return Err(FormatError::NonZeroReserved {
1825 structure: "CriticalMetadataRecoveryShard",
1826 });
1827 }
1828 let shard_role = bytes[6];
1829 if shard_role > 1 {
1830 return Err(FormatError::InvalidArchive(
1831 "CriticalMetadataRecoveryShard has unknown role",
1832 ));
1833 }
1834 expect_crc(
1835 "CriticalMetadataRecoveryShard",
1836 &[
1837 &bytes[..12],
1838 &bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..],
1839 ]
1840 .concat(),
1841 read_u32(bytes, 12)?,
1842 )?;
1843 let shard_payload_length = read_u32(bytes, 8)?;
1844 if shard_payload_length as usize > shard_size {
1845 return Err(FormatError::InvalidArchive(
1846 "CriticalMetadataRecoveryShard payload length exceeds shard size",
1847 ));
1848 }
1849 Ok(Self {
1850 shard_index: read_u16(bytes, 4)?,
1851 shard_role,
1852 shard_payload_length,
1853 payload: bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..].to_vec(),
1854 shard_crc32c: read_u32(bytes, 12)?,
1855 })
1856 }
1857
1858 pub fn to_bytes(&self, shard_size: usize) -> Result<Vec<u8>, FormatError> {
1859 if self.payload.len() != shard_size {
1860 return Err(FormatError::InvalidArchive(
1861 "CriticalMetadataRecoveryShard payload length mismatch",
1862 ));
1863 }
1864 let mut bytes = vec![0u8; CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN + shard_size];
1865 bytes[0..4].copy_from_slice(&TZCS_MAGIC);
1866 write_u16(&mut bytes, 4, self.shard_index);
1867 bytes[6] = self.shard_role;
1868 write_u32(&mut bytes, 8, self.shard_payload_length);
1869 bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..].copy_from_slice(&self.payload);
1870 let mut covered = Vec::with_capacity(12 + shard_size);
1871 covered.extend_from_slice(&bytes[..12]);
1872 covered.extend_from_slice(&bytes[CRITICAL_METADATA_RECOVERY_SHARD_HEADER_LEN..]);
1873 let crc = crc32c(&covered);
1874 write_u32(&mut bytes, 12, crc);
1875 Ok(bytes)
1876 }
1877}
1878
1879#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1880pub struct CriticalRecoveryLocator {
1881 pub volume_format_rev: u16,
1882 pub cmra_offset: u64,
1883 pub cmra_length: u32,
1884 pub volume_trailer_offset: u64,
1885 pub body_bytes_before_cmra: u64,
1886 pub archive_uuid_hint: [u8; 16],
1887 pub session_id_hint: [u8; 16],
1888 pub volume_index_hint: u32,
1889 pub locator_sequence: u32,
1890 pub cmra_shard_size: u32,
1891 pub cmra_data_shard_count: u16,
1892 pub cmra_parity_shard_count: u16,
1893 pub cmra_image_length: u32,
1894 pub cmra_image_sha256: [u8; 32],
1895 pub locator_crc32c: u32,
1896}
1897
1898impl CriticalRecoveryLocator {
1899 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
1900 expect_len(
1901 "CriticalRecoveryLocator",
1902 CRITICAL_RECOVERY_LOCATOR_LEN,
1903 bytes.len(),
1904 )?;
1905 expect_magic("CriticalRecoveryLocator", TZCL_MAGIC, &bytes[0..4])?;
1906 let version = read_u16(bytes, 4)?;
1907 if version != 1 {
1908 return Err(FormatError::UnsupportedFormatVersion(version));
1909 }
1910 let volume_format_rev = read_u16(bytes, 6)?;
1911 if !matches!(
1912 volume_format_rev,
1913 VOLUME_FORMAT_REV_43 | VOLUME_FORMAT_REV_44
1914 ) {
1915 return Err(FormatError::UnsupportedVolumeFormatRevision {
1916 format_version: FORMAT_VERSION,
1917 volume_format_rev,
1918 reader_max_supported_revision: READER_MAX_SUPPORTED_VOLUME_FORMAT_REV,
1919 });
1920 }
1921 if read_u16(bytes, 20)? != CRITICAL_METADATA_RECOVERY_HEADER_LEN as u16 {
1922 return Err(FormatError::InvalidArchive(
1923 "CriticalRecoveryLocator CMRA header length is invalid",
1924 ));
1925 }
1926 let fec_algo = read_u16(bytes, 22)?;
1927 if fec_algo != FecAlgo::ReedSolomonGF16 as u16 {
1928 return Err(FormatError::UnknownFecAlgo(fec_algo));
1929 }
1930 let locator_sequence = read_u32(bytes, 76)?;
1931 if locator_sequence > 1 {
1932 return Err(FormatError::InvalidArchive(
1933 "CriticalRecoveryLocator has invalid sequence",
1934 ));
1935 }
1936 expect_crc(
1937 "CriticalRecoveryLocator",
1938 &bytes[..124],
1939 read_u32(bytes, 124)?,
1940 )?;
1941
1942 Ok(Self {
1943 volume_format_rev,
1944 cmra_offset: read_u64(bytes, 8)?,
1945 cmra_length: read_u32(bytes, 16)?,
1946 volume_trailer_offset: read_u64(bytes, 24)?,
1947 body_bytes_before_cmra: read_u64(bytes, 32)?,
1948 archive_uuid_hint: read_array_16(bytes, 40)?,
1949 session_id_hint: read_array_16(bytes, 56)?,
1950 volume_index_hint: read_u32(bytes, 72)?,
1951 locator_sequence,
1952 cmra_shard_size: read_u32(bytes, 80)?,
1953 cmra_data_shard_count: read_u16(bytes, 84)?,
1954 cmra_parity_shard_count: read_u16(bytes, 86)?,
1955 cmra_image_length: read_u32(bytes, 88)?,
1956 cmra_image_sha256: read_array_32(bytes, 92)?,
1957 locator_crc32c: read_u32(bytes, 124)?,
1958 })
1959 }
1960
1961 pub fn to_bytes(&self) -> [u8; CRITICAL_RECOVERY_LOCATOR_LEN] {
1962 let mut bytes = [0u8; CRITICAL_RECOVERY_LOCATOR_LEN];
1963 bytes[0..4].copy_from_slice(&TZCL_MAGIC);
1964 write_u16(&mut bytes, 4, 1);
1965 write_u16(&mut bytes, 6, self.volume_format_rev);
1966 write_u64(&mut bytes, 8, self.cmra_offset);
1967 write_u32(&mut bytes, 16, self.cmra_length);
1968 write_u16(&mut bytes, 20, CRITICAL_METADATA_RECOVERY_HEADER_LEN as u16);
1969 write_u16(&mut bytes, 22, FecAlgo::ReedSolomonGF16 as u16);
1970 write_u64(&mut bytes, 24, self.volume_trailer_offset);
1971 write_u64(&mut bytes, 32, self.body_bytes_before_cmra);
1972 bytes[40..56].copy_from_slice(&self.archive_uuid_hint);
1973 bytes[56..72].copy_from_slice(&self.session_id_hint);
1974 write_u32(&mut bytes, 72, self.volume_index_hint);
1975 write_u32(&mut bytes, 76, self.locator_sequence);
1976 write_u32(&mut bytes, 80, self.cmra_shard_size);
1977 write_u16(&mut bytes, 84, self.cmra_data_shard_count);
1978 write_u16(&mut bytes, 86, self.cmra_parity_shard_count);
1979 write_u32(&mut bytes, 88, self.cmra_image_length);
1980 bytes[92..124].copy_from_slice(&self.cmra_image_sha256);
1981 let crc = crc32c(&bytes[..124]);
1982 write_u32(&mut bytes, 124, crc);
1983 bytes
1984 }
1985}
1986
1987#[derive(Debug, Clone, PartialEq, Eq)]
1988pub struct BootstrapSidecarHeader {
1989 pub archive_uuid: [u8; 16],
1990 pub session_id: [u8; 16],
1991 pub flags: u32,
1992 pub manifest_footer_offset: u64,
1993 pub manifest_footer_length: u32,
1994 pub index_root_records_offset: u64,
1995 pub index_root_records_length: u64,
1996 pub dictionary_records_offset: u64,
1997 pub dictionary_records_length: u64,
1998 pub sidecar_hmac: [u8; 32],
1999 pub header_crc32c: u32,
2000}
2001
2002impl BootstrapSidecarHeader {
2003 pub fn parse(bytes: &[u8]) -> Result<Self, FormatError> {
2004 expect_len(
2005 "BootstrapSidecarHeader",
2006 BOOTSTRAP_SIDECAR_HEADER_LEN,
2007 bytes.len(),
2008 )?;
2009 expect_magic("BootstrapSidecarHeader", TZBS_MAGIC, &bytes[0..4])?;
2010 let version = read_u32(bytes, 4)?;
2011 if version != 1 {
2012 return Err(FormatError::UnsupportedBootstrapSidecarVersion(version));
2013 }
2014 expect_zero("BootstrapSidecarHeader", &bytes[88..92])?;
2015 expect_crc(
2016 "BootstrapSidecarHeader",
2017 &bytes[..124],
2018 read_u32(bytes, 124)?,
2019 )?;
2020
2021 let header = Self {
2022 archive_uuid: read_array_16(bytes, 8)?,
2023 session_id: read_array_16(bytes, 24)?,
2024 flags: read_u32(bytes, 40)?,
2025 manifest_footer_offset: read_u64(bytes, 44)?,
2026 manifest_footer_length: read_u32(bytes, 52)?,
2027 index_root_records_offset: read_u64(bytes, 56)?,
2028 index_root_records_length: read_u64(bytes, 64)?,
2029 dictionary_records_offset: read_u64(bytes, 72)?,
2030 dictionary_records_length: read_u64(bytes, 80)?,
2031 sidecar_hmac: read_array_32(bytes, 92)?,
2032 header_crc32c: read_u32(bytes, 124)?,
2033 };
2034 header.validate_sections()?;
2035 Ok(header)
2036 }
2037
2038 pub fn validate_packed_layout(&self, file_size: u64) -> Result<(), FormatError> {
2039 let mut cursor = BOOTSTRAP_SIDECAR_HEADER_LEN as u64;
2040 cursor = self.validate_section_cursor(
2041 self.has_manifest_footer(),
2042 self.manifest_footer_offset,
2043 self.manifest_footer_length as u64,
2044 cursor,
2045 )?;
2046 cursor = self.validate_section_cursor(
2047 self.has_index_root_records(),
2048 self.index_root_records_offset,
2049 self.index_root_records_length,
2050 cursor,
2051 )?;
2052 cursor = self.validate_section_cursor(
2053 self.has_dictionary_records(),
2054 self.dictionary_records_offset,
2055 self.dictionary_records_length,
2056 cursor,
2057 )?;
2058 if cursor != file_size {
2059 return Err(FormatError::NonCanonicalBootstrapSidecarLayout);
2060 }
2061 Ok(())
2062 }
2063
2064 pub fn has_manifest_footer(&self) -> bool {
2065 self.flags & SIDECAR_MANIFEST_PRESENT != 0
2066 }
2067
2068 pub fn has_index_root_records(&self) -> bool {
2069 self.flags & SIDECAR_INDEX_ROOT_PRESENT != 0
2070 }
2071
2072 pub fn has_dictionary_records(&self) -> bool {
2073 self.flags & SIDECAR_DICTIONARY_PRESENT != 0
2074 }
2075
2076 pub fn to_bytes(&self) -> [u8; BOOTSTRAP_SIDECAR_HEADER_LEN] {
2077 let mut bytes = [0u8; BOOTSTRAP_SIDECAR_HEADER_LEN];
2078 bytes[0..4].copy_from_slice(&TZBS_MAGIC);
2079 write_u32(&mut bytes, 4, 1);
2080 bytes[8..24].copy_from_slice(&self.archive_uuid);
2081 bytes[24..40].copy_from_slice(&self.session_id);
2082 write_u32(&mut bytes, 40, self.flags);
2083 write_u64(&mut bytes, 44, self.manifest_footer_offset);
2084 write_u32(&mut bytes, 52, self.manifest_footer_length);
2085 write_u64(&mut bytes, 56, self.index_root_records_offset);
2086 write_u64(&mut bytes, 64, self.index_root_records_length);
2087 write_u64(&mut bytes, 72, self.dictionary_records_offset);
2088 write_u64(&mut bytes, 80, self.dictionary_records_length);
2089 bytes[92..124].copy_from_slice(&self.sidecar_hmac);
2090 let crc = crc32c(&bytes[..124]);
2091 write_u32(&mut bytes, 124, crc);
2092 bytes
2093 }
2094
2095 fn validate_sections(&self) -> Result<(), FormatError> {
2096 if self.flags & !SIDECAR_KNOWN_FLAGS != 0 {
2097 return Err(FormatError::UnknownBootstrapSidecarFlags(self.flags));
2098 }
2099 self.validate_presence_fields(
2100 self.has_manifest_footer(),
2101 self.manifest_footer_offset,
2102 self.manifest_footer_length as u64,
2103 )?;
2104 if self.has_manifest_footer() && self.manifest_footer_length != MANIFEST_FOOTER_LEN as u32 {
2105 return Err(FormatError::InvalidManifestFooterLength(
2106 self.manifest_footer_length,
2107 ));
2108 }
2109 self.validate_presence_fields(
2110 self.has_index_root_records(),
2111 self.index_root_records_offset,
2112 self.index_root_records_length,
2113 )?;
2114 self.validate_presence_fields(
2115 self.has_dictionary_records(),
2116 self.dictionary_records_offset,
2117 self.dictionary_records_length,
2118 )?;
2119 Ok(())
2120 }
2121
2122 fn validate_presence_fields(
2123 &self,
2124 present: bool,
2125 offset: u64,
2126 length: u64,
2127 ) -> Result<(), FormatError> {
2128 match (present, offset, length) {
2129 (true, 0, _) | (true, _, 0) => Err(FormatError::EmptyBootstrapSidecarSection),
2130 (false, 0, 0) => Ok(()),
2131 (false, _, _) => Err(FormatError::NonZeroAbsentBootstrapSidecarSection),
2132 (true, _, _) => Ok(()),
2133 }
2134 }
2135
2136 fn validate_section_cursor(
2137 &self,
2138 present: bool,
2139 offset: u64,
2140 length: u64,
2141 cursor: u64,
2142 ) -> Result<u64, FormatError> {
2143 if present {
2144 if offset != cursor {
2145 return Err(FormatError::NonCanonicalBootstrapSidecarLayout);
2146 }
2147 cursor
2148 .checked_add(length)
2149 .ok_or(FormatError::NonCanonicalBootstrapSidecarLayout)
2150 } else {
2151 Ok(cursor)
2152 }
2153 }
2154}
2155
2156fn is_known_extension(ext_tag: u16) -> bool {
2157 matches!(ext_tag, 0x0001 | 0x0002 | 0x0003 | 0x0005)
2158}
2159
2160fn validate_known_extension(ext_tag: u16, value: &[u8]) -> Result<(), FormatError> {
2161 match ext_tag {
2162 0x0001 | 0x0002 | 0x0005 => std::str::from_utf8(value)
2163 .map(|_| ())
2164 .map_err(|_| FormatError::MalformedKnownExtension(ext_tag)),
2165 0x0003 => {
2166 if value.len() == 8 {
2167 Ok(())
2168 } else {
2169 Err(FormatError::MalformedKnownExtension(ext_tag))
2170 }
2171 }
2172 _ => Ok(()),
2173 }
2174}
2175
2176fn expect_len(structure: &'static str, expected: usize, actual: usize) -> Result<(), FormatError> {
2177 if actual != expected {
2178 return Err(FormatError::InvalidLength {
2179 structure,
2180 expected,
2181 actual,
2182 });
2183 }
2184 Ok(())
2185}
2186
2187fn u32_len(value: usize, field: &'static str) -> Result<u32, FormatError> {
2188 u32::try_from(value).map_err(|_| FormatError::WriterUnsupported(field))
2189}
2190
2191fn expect_magic(
2192 structure: &'static str,
2193 expected: [u8; 4],
2194 actual: &[u8],
2195) -> Result<(), FormatError> {
2196 if actual != expected {
2197 return Err(FormatError::BadMagic { structure });
2198 }
2199 Ok(())
2200}
2201
2202fn expect_zero(structure: &'static str, bytes: &[u8]) -> Result<(), FormatError> {
2203 if bytes.iter().any(|byte| *byte != 0) {
2204 return Err(FormatError::NonZeroReserved { structure });
2205 }
2206 Ok(())
2207}
2208
2209fn expect_crc(structure: &'static str, covered: &[u8], expected: u32) -> Result<(), FormatError> {
2210 if crc32c(covered) != expected {
2211 return Err(FormatError::BadCrc { structure });
2212 }
2213 Ok(())
2214}
2215
2216fn read_array_16(bytes: &[u8], offset: usize) -> Result<[u8; 16], FormatError> {
2217 let mut value = [0u8; 16];
2218 value.copy_from_slice(
2219 bytes
2220 .get(offset..offset + 16)
2221 .ok_or(FormatError::InvalidLength {
2222 structure: "array16",
2223 expected: offset + 16,
2224 actual: bytes.len(),
2225 })?,
2226 );
2227 Ok(value)
2228}
2229
2230fn read_array_24(bytes: &[u8], offset: usize) -> Result<[u8; 24], FormatError> {
2231 let mut value = [0u8; 24];
2232 value.copy_from_slice(
2233 bytes
2234 .get(offset..offset + 24)
2235 .ok_or(FormatError::InvalidLength {
2236 structure: "array24",
2237 expected: offset + 24,
2238 actual: bytes.len(),
2239 })?,
2240 );
2241 Ok(value)
2242}
2243
2244fn read_array_32(bytes: &[u8], offset: usize) -> Result<[u8; 32], FormatError> {
2245 let mut value = [0u8; 32];
2246 value.copy_from_slice(
2247 bytes
2248 .get(offset..offset + 32)
2249 .ok_or(FormatError::InvalidLength {
2250 structure: "array32",
2251 expected: offset + 32,
2252 actual: bytes.len(),
2253 })?,
2254 );
2255 Ok(value)
2256}
2257
2258fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, FormatError> {
2259 let array: [u8; 2] = bytes
2260 .get(offset..offset + 2)
2261 .ok_or(FormatError::InvalidLength {
2262 structure: "u16",
2263 expected: offset + 2,
2264 actual: bytes.len(),
2265 })?
2266 .try_into()
2267 .expect("slice length checked");
2268 Ok(u16::from_le_bytes(array))
2269}
2270
2271fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, FormatError> {
2272 let array: [u8; 4] = bytes
2273 .get(offset..offset + 4)
2274 .ok_or(FormatError::InvalidLength {
2275 structure: "u32",
2276 expected: offset + 4,
2277 actual: bytes.len(),
2278 })?
2279 .try_into()
2280 .expect("slice length checked");
2281 Ok(u32::from_le_bytes(array))
2282}
2283
2284fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, FormatError> {
2285 let array: [u8; 8] = bytes
2286 .get(offset..offset + 8)
2287 .ok_or(FormatError::InvalidLength {
2288 structure: "u64",
2289 expected: offset + 8,
2290 actual: bytes.len(),
2291 })?
2292 .try_into()
2293 .expect("slice length checked");
2294 Ok(u64::from_le_bytes(array))
2295}
2296
2297fn read_i64(bytes: &[u8], offset: usize) -> Result<i64, FormatError> {
2298 let array: [u8; 8] = bytes
2299 .get(offset..offset + 8)
2300 .ok_or(FormatError::InvalidLength {
2301 structure: "i64",
2302 expected: offset + 8,
2303 actual: bytes.len(),
2304 })?
2305 .try_into()
2306 .expect("slice length checked");
2307 Ok(i64::from_le_bytes(array))
2308}
2309
2310fn write_u16(bytes: &mut [u8], offset: usize, value: u16) {
2311 bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
2312}
2313
2314fn write_u32(bytes: &mut [u8], offset: usize, value: u32) {
2315 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
2316}
2317
2318fn write_u64(bytes: &mut [u8], offset: usize, value: u64) {
2319 bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
2320}
2321
2322fn write_i64(bytes: &mut [u8], offset: usize, value: i64) {
2323 bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
2324}
2325
2326#[cfg(test)]
2327mod tests {
2328 use super::*;
2329 use crate::format::{CRYPTO_HEADER_HMAC_LEN, VOLUME_FORMAT_REV, VOLUME_FORMAT_REV_43};
2330 use sha2::{Digest, Sha256};
2331
2332 fn uuid() -> [u8; 16] {
2333 [0x11; 16]
2334 }
2335
2336 fn session() -> [u8; 16] {
2337 [0x22; 16]
2338 }
2339
2340 fn volume_header() -> VolumeHeader {
2341 VolumeHeader {
2342 format_version: FORMAT_VERSION,
2343 volume_format_rev: VOLUME_FORMAT_REV,
2344 volume_index: 0,
2345 stripe_width: 1,
2346 archive_uuid: uuid(),
2347 session_id: session(),
2348 crypto_header_offset: VOLUME_HEADER_LEN as u32,
2349 crypto_header_length: 128,
2350 header_crc32c: 0,
2351 }
2352 }
2353
2354 fn crypto_fixed() -> CryptoHeaderFixed {
2355 CryptoHeaderFixed {
2356 length: (CRYPTO_HEADER_FIXED_LEN + 2 + 6 + CRYPTO_HEADER_HMAC_LEN) as u32,
2357 compression_algo: CompressionAlgo::ZstdFramed,
2358 aead_algo: AeadAlgo::AesGcmSiv256,
2359 fec_algo: FecAlgo::ReedSolomonGF16,
2360 kdf_algo: KdfAlgo::Raw,
2361 chunk_size: 262_144,
2362 envelope_target_size: 4 * 1024 * 1024,
2363 block_size: 4096,
2364 fec_data_shards: 16,
2365 fec_parity_shards: 2,
2366 index_fec_data_shards: 16,
2367 index_fec_parity_shards: 2,
2368 index_root_fec_data_shards: 16,
2369 index_root_fec_parity_shards: 2,
2370 stripe_width: 1,
2371 volume_loss_tolerance: 0,
2372 bit_rot_buffer_pct: 5,
2373 has_dictionary: 0,
2374 max_path_length: 4096,
2375 expected_volume_size: 0,
2376 }
2377 }
2378
2379 fn recipient_record_bytes() -> Vec<u8> {
2380 recipient_record_bytes_with_profile_and_payload(1, b"alice", b"profile")
2381 }
2382
2383 fn recipient_record_bytes_with_profile_and_payload(
2384 profile_id: u16,
2385 identity: &[u8],
2386 profile_payload: &[u8],
2387 ) -> Vec<u8> {
2388 let mut digest = Sha256::new();
2389 digest.update(identity);
2390 let identity_digest = digest.finalize();
2391
2392 let mut bytes = vec![0u8; KEY_WRAP_RECORD_HEADER_LEN];
2393 let record_length = KEY_WRAP_RECORD_HEADER_LEN
2394 .checked_add(identity.len())
2395 .and_then(|value| value.checked_add(profile_payload.len()))
2396 .unwrap() as u32;
2397 write_u16(&mut bytes, 4, profile_id);
2398 write_u16(&mut bytes, 6, 2);
2399 write_u32(&mut bytes, 12, identity.len() as u32);
2400 write_u32(&mut bytes, 16, profile_payload.len() as u32);
2401 bytes[20..52].copy_from_slice(&identity_digest);
2402 bytes.extend_from_slice(identity);
2403 bytes.extend_from_slice(profile_payload);
2404 write_u32(&mut bytes, 0, record_length);
2405 bytes
2406 }
2407
2408 fn key_wrap_table_bytes() -> (Vec<u8>, u32) {
2409 let records = recipient_record_bytes();
2410 let record_count = 1u32;
2411 let records_length = records.len() as u32;
2412 let table_length = KEY_WRAP_TABLE_HEADER_LEN as u32 + records_length;
2413
2414 let mut bytes = vec![0u8; KEY_WRAP_TABLE_HEADER_LEN];
2415 bytes[0..4].copy_from_slice(&TZKW_MAGIC);
2416 write_u16(&mut bytes, 4, KEY_WRAP_TABLE_VERSION);
2417 write_u16(&mut bytes, 6, VOLUME_FORMAT_REV_44);
2418 write_u32(&mut bytes, 8, table_length);
2419 write_u32(&mut bytes, 12, KEY_WRAP_TABLE_HEADER_LEN as u32);
2420 bytes[20..36].copy_from_slice(&uuid());
2421 bytes[36..52].copy_from_slice(&session());
2422 write_u32(&mut bytes, 52, record_count);
2423 write_u32(&mut bytes, 56, KEY_WRAP_TABLE_HEADER_LEN as u32);
2424 write_u32(&mut bytes, 60, records_length);
2425 bytes.extend(records);
2426 (bytes, table_length)
2427 }
2428
2429 fn raw_crypto_header_bytes() -> Vec<u8> {
2430 let fixed = crypto_fixed();
2431 let mut bytes = Vec::new();
2432 bytes.extend_from_slice(&fixed.to_bytes());
2433 bytes.extend_from_slice(&(KdfAlgo::Raw as u16).to_le_bytes());
2434 bytes.extend_from_slice(&0u16.to_le_bytes());
2435 bytes.extend_from_slice(&0u32.to_le_bytes());
2436 bytes.extend_from_slice(&[0xab; CRYPTO_HEADER_HMAC_LEN]);
2437 bytes
2438 }
2439
2440 #[test]
2441 fn volume_header_round_trips_and_validates() {
2442 let bytes = volume_header().to_bytes();
2443 let parsed = VolumeHeader::parse(&bytes).unwrap();
2444 assert_eq!(parsed.format_version, FORMAT_VERSION);
2445 assert_eq!(parsed.volume_format_rev, VOLUME_FORMAT_REV);
2446 assert_eq!(parsed.crypto_header_offset, VOLUME_HEADER_LEN as u32);
2447 }
2448
2449 #[test]
2450 fn volume_header_rejects_mutations() {
2451 let mut bytes = volume_header().to_bytes();
2452 bytes[0] = b'X';
2453 assert_eq!(
2454 VolumeHeader::parse(&bytes).unwrap_err(),
2455 FormatError::BadMagic {
2456 structure: "VolumeHeader"
2457 }
2458 );
2459
2460 let mut bytes = volume_header().to_bytes();
2461 write_u16(&mut bytes, 6, 35);
2462 let crc = crc32c(&bytes[..124]);
2463 write_u32(&mut bytes, 124, crc);
2464 assert_eq!(
2465 VolumeHeader::parse(&bytes)
2466 .unwrap()
2467 .parse_volume_format_revision()
2468 .unwrap_err(),
2469 FormatError::UnsupportedVolumeFormatRevision {
2470 format_version: FORMAT_VERSION,
2471 volume_format_rev: 35,
2472 reader_max_supported_revision: READER_MAX_SUPPORTED_VOLUME_FORMAT_REV,
2473 }
2474 );
2475
2476 let mut bytes = volume_header().to_bytes();
2477 bytes[124] ^= 1;
2478 assert_eq!(
2479 VolumeHeader::parse(&bytes).unwrap_err(),
2480 FormatError::BadCrc {
2481 structure: "VolumeHeader"
2482 }
2483 );
2484
2485 let mut bytes = volume_header().to_bytes();
2486 write_u32(&mut bytes, 48, 129);
2487 let crc = crc32c(&bytes[..124]);
2488 write_u32(&mut bytes, 124, crc);
2489 assert_eq!(
2490 VolumeHeader::parse(&bytes).unwrap_err(),
2491 FormatError::NonCanonicalCryptoHeaderOffset(129)
2492 );
2493
2494 let mut bytes = volume_header().to_bytes();
2495 write_u32(&mut bytes, 52, READER_MAX_CRYPTO_HEADER_LEN + 1);
2496 let crc = crc32c(&bytes[..124]);
2497 write_u32(&mut bytes, 124, crc);
2498 assert_eq!(
2499 VolumeHeader::parse(&bytes).unwrap_err(),
2500 FormatError::ReaderResourceLimitExceeded {
2501 field: "CryptoHeader length",
2502 cap: READER_MAX_CRYPTO_HEADER_LEN as u64,
2503 actual: (READER_MAX_CRYPTO_HEADER_LEN + 1) as u64,
2504 }
2505 );
2506 }
2507
2508 #[test]
2509 fn volume_header_revision_dispatch_is_versioned() {
2510 let bytes = volume_header().to_bytes();
2511 let parsed = VolumeHeader::parse(&bytes).unwrap();
2512 assert_eq!(
2513 parsed.parse_volume_format_revision().unwrap(),
2514 VolumeFormatRevision::V44
2515 );
2516
2517 let mut v43 = volume_header().to_bytes();
2518 write_u16(&mut v43, 6, VOLUME_FORMAT_REV_43);
2519 let v43_crc = crc32c(&v43[..124]);
2520 write_u32(&mut v43, 124, v43_crc);
2521 let parsed = VolumeHeader::parse(&v43).unwrap();
2522 assert_eq!(
2523 parsed.parse_volume_format_revision().unwrap(),
2524 VolumeFormatRevision::V43
2525 );
2526
2527 let mut v45 = volume_header().to_bytes();
2528 write_u16(&mut v45, 6, VOLUME_FORMAT_REV_44 + 1);
2529 let v45_crc = crc32c(&v45[..124]);
2530 write_u32(&mut v45, 124, v45_crc);
2531 assert_eq!(
2532 VolumeHeader::parse(&v45)
2533 .unwrap()
2534 .parse_volume_format_revision(),
2535 Err(FormatError::UnsupportedVolumeFormatRevision {
2536 format_version: FORMAT_VERSION,
2537 volume_format_rev: VOLUME_FORMAT_REV_44 + 1,
2538 reader_max_supported_revision: READER_MAX_SUPPORTED_VOLUME_FORMAT_REV,
2539 })
2540 );
2541 }
2542
2543 #[test]
2544 fn fixed_structure_magic_matrix_rejects_all_magic_fields() {
2545 let mut volume = volume_header().to_bytes();
2546 volume[0] ^= 0x01;
2547 assert_eq!(
2548 VolumeHeader::parse(&volume).unwrap_err(),
2549 FormatError::BadMagic {
2550 structure: "VolumeHeader"
2551 }
2552 );
2553
2554 let mut crypto = crypto_fixed().to_bytes();
2555 crypto[0] ^= 0x01;
2556 assert_eq!(
2557 CryptoHeaderFixed::parse(&crypto, crypto_fixed().length).unwrap_err(),
2558 FormatError::BadMagic {
2559 structure: "CryptoHeaderFixed"
2560 }
2561 );
2562
2563 let record = BlockRecord {
2564 block_index: 0,
2565 kind: BlockKind::PayloadData,
2566 flags: BLOCK_LAST_DATA_FLAG,
2567 payload: vec![7; 4096],
2568 record_crc32c: 0,
2569 };
2570 let mut block = record.to_bytes();
2571 block[0] ^= 0x01;
2572 assert_eq!(
2573 BlockRecord::parse(&block, 4096).unwrap_err(),
2574 FormatError::BadMagic {
2575 structure: "BlockRecord"
2576 }
2577 );
2578
2579 let footer = ManifestFooter {
2580 archive_uuid: uuid(),
2581 session_id: session(),
2582 volume_index: 0,
2583 is_authoritative: 1,
2584 total_volumes: 1,
2585 index_root_first_block: 0,
2586 index_root_data_block_count: 1,
2587 index_root_parity_block_count: 0,
2588 index_root_encrypted_size: 4096,
2589 index_root_decompressed_size: 120,
2590 manifest_hmac: [0xaa; 32],
2591 };
2592 let mut manifest = footer.to_bytes();
2593 manifest[0] ^= 0x01;
2594 assert_eq!(
2595 ManifestFooter::parse(&manifest).unwrap_err(),
2596 FormatError::BadMagic {
2597 structure: "ManifestFooter"
2598 }
2599 );
2600
2601 let trailer = VolumeTrailer {
2602 archive_uuid: uuid(),
2603 session_id: session(),
2604 volume_index: 0,
2605 block_count: 3,
2606 bytes_written: 10_000,
2607 manifest_footer_offset: 9_864,
2608 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
2609 closed_at_ns: 123,
2610 root_auth_footer_offset: 0,
2611 root_auth_footer_length: 0,
2612 root_auth_flags: 0,
2613 trailer_hmac: [0xbb; 32],
2614 };
2615 let mut trailer_bytes = trailer.to_bytes();
2616 trailer_bytes[0] ^= 0x01;
2617 assert_eq!(
2618 VolumeTrailer::parse(&trailer_bytes).unwrap_err(),
2619 FormatError::BadMagic {
2620 structure: "VolumeTrailer"
2621 }
2622 );
2623
2624 let sidecar = BootstrapSidecarHeader {
2625 archive_uuid: uuid(),
2626 session_id: session(),
2627 flags: SIDECAR_MANIFEST_PRESENT,
2628 manifest_footer_offset: BOOTSTRAP_SIDECAR_HEADER_LEN as u64,
2629 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
2630 index_root_records_offset: 0,
2631 index_root_records_length: 0,
2632 dictionary_records_offset: 0,
2633 dictionary_records_length: 0,
2634 sidecar_hmac: [0xcc; 32],
2635 header_crc32c: 0,
2636 };
2637 let mut sidecar_bytes = sidecar.to_bytes();
2638 sidecar_bytes[0] ^= 0x01;
2639 assert_eq!(
2640 BootstrapSidecarHeader::parse(&sidecar_bytes).unwrap_err(),
2641 FormatError::BadMagic {
2642 structure: "BootstrapSidecarHeader"
2643 }
2644 );
2645 }
2646
2647 #[test]
2648 fn crypto_header_fixed_round_trips_and_validates() {
2649 let header = crypto_fixed();
2650 let bytes = header.to_bytes();
2651 let parsed = CryptoHeaderFixed::parse(&bytes, header.length).unwrap();
2652 assert_eq!(parsed.compression_algo, CompressionAlgo::ZstdFramed);
2653 assert_eq!(parsed.fec_algo, FecAlgo::ReedSolomonGF16);
2654 }
2655
2656 #[test]
2657 fn crypto_header_fixed_rejects_unsupported_profile_values() {
2658 let mut header = crypto_fixed();
2659 header.compression_algo = CompressionAlgo::None;
2660 assert_eq!(
2661 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2662 FormatError::UnsupportedCompression(CompressionAlgo::None)
2663 );
2664
2665 let mut header = crypto_fixed();
2666 header.block_size = 4097;
2667 assert_eq!(
2668 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
2669 FormatError::OddBlockSize(4097)
2670 );
2671
2672 let mut bytes = crypto_fixed().to_bytes();
2673 bytes[47] = 1;
2674 assert_eq!(
2675 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2676 FormatError::NonZeroReserved {
2677 structure: "CryptoHeaderFixed"
2678 }
2679 );
2680 }
2681
2682 #[test]
2683 fn key_wrap_table_round_trips_with_record_digest_checks() {
2684 let (table_bytes, table_length) = key_wrap_table_bytes();
2685 let parsed =
2686 KeyWrapTableV1::parse(&table_bytes, &uuid(), &session(), table_length, 1).unwrap();
2687 assert_eq!(parsed.table_length, table_length);
2688 assert_eq!(parsed.recipient_records.len(), 1);
2689 assert_eq!(
2690 parsed.recipient_records[0].recipient_identity_bytes,
2691 b"alice"
2692 );
2693
2694 let (record, consumed) = RecipientRecordV1::parse(&table_bytes[96..]).unwrap();
2695 assert_eq!(record.profile_id, 1);
2696 assert_eq!(record.recipient_identity_type, 2);
2697 assert_eq!(record.profile_payload_bytes, b"profile");
2698 assert_eq!(record.recipient_identity_length as usize, b"alice".len());
2699 assert_eq!(record.profile_payload_length as usize, b"profile".len());
2700 assert_eq!(consumed, table_bytes.len() - 96);
2701
2702 let digest = compute_key_wrap_table_digest(table_length, &table_bytes);
2703 let mut expected = Sha256::new();
2704 expected.update(KEY_WRAP_TABLE_DIGEST_DOMAIN_V44);
2705 expected.update(table_length.to_le_bytes());
2706 expected.update(&table_bytes);
2707 let expected = expected.finalize();
2708 let expected_digest: [u8; 32] = expected[..].try_into().unwrap();
2709 assert_eq!(digest, expected_digest);
2710 }
2711
2712 #[test]
2713 fn key_wrap_table_parse_rejects_table_record_count_and_length_mismatch() {
2714 let (table_bytes, table_length) = key_wrap_table_bytes();
2715 let parsed = KeyWrapTableV1::parse(&table_bytes, &uuid(), &session(), table_length, 2);
2716 assert_eq!(
2717 parsed.unwrap_err(),
2718 FormatError::InvalidArchive(
2719 "KeyWrapTableV1 recipient_record_count does not match KdfParams"
2720 )
2721 );
2722
2723 let mut bad = table_bytes.clone();
2724 write_u32(&mut bad, 8, table_length + 1);
2725 assert_eq!(
2726 KeyWrapTableV1::parse(&bad, &uuid(), &session(), table_length, 1),
2727 Err(FormatError::InvalidArchive(
2728 "KeyWrapTableV1 length does not match KdfParams"
2729 ))
2730 );
2731 }
2732
2733 #[test]
2734 fn key_wrap_table_parse_rejects_short_table_without_panic() {
2735 assert_eq!(
2736 KeyWrapTableV1::parse(&[0u8; 3], &uuid(), &session(), 3, 0),
2737 Err(FormatError::InvalidLength {
2738 structure: "KeyWrapTableV1",
2739 expected: KEY_WRAP_TABLE_HEADER_LEN,
2740 actual: 3,
2741 })
2742 );
2743 }
2744
2745 #[test]
2746 fn key_wrap_table_parse_rejects_magic_version_revision_and_reserved_fields() {
2747 let (table_bytes, table_length) = key_wrap_table_bytes();
2748
2749 let mut bad_magic = table_bytes.clone();
2750 bad_magic[0] = b'X';
2751 assert_eq!(
2752 KeyWrapTableV1::parse(&bad_magic, &uuid(), &session(), table_length, 1),
2753 Err(FormatError::BadMagic {
2754 structure: "KeyWrapTableV1"
2755 })
2756 );
2757
2758 let mut bad_version = table_bytes.clone();
2759 write_u16(&mut bad_version, 4, 2);
2760 assert_eq!(
2761 KeyWrapTableV1::parse(&bad_version, &uuid(), &session(), table_length, 1),
2762 Err(FormatError::InvalidArchive(
2763 "KeyWrapTableV1 version must be 1"
2764 ))
2765 );
2766
2767 let mut bad_revision = table_bytes.clone();
2768 write_u16(&mut bad_revision, 6, VOLUME_FORMAT_REV_43);
2769 assert_eq!(
2770 KeyWrapTableV1::parse(&bad_revision, &uuid(), &session(), table_length, 1),
2771 Err(FormatError::InvalidArchive(
2772 "KeyWrapTableV1 volume_format_rev must be 44"
2773 ))
2774 );
2775
2776 let mut bad_reserved = table_bytes.clone();
2777 bad_reserved[64] = 1;
2778 assert_eq!(
2779 KeyWrapTableV1::parse(&bad_reserved, &uuid(), &session(), table_length, 1),
2780 Err(FormatError::NonZeroReserved {
2781 structure: "KeyWrapTableV1"
2782 })
2783 );
2784
2785 let mut bad_header_length = table_bytes.clone();
2786 write_u32(&mut bad_header_length, 12, 97);
2787 assert_eq!(
2788 KeyWrapTableV1::parse(&bad_header_length, &uuid(), &session(), table_length, 1),
2789 Err(FormatError::InvalidArchive(
2790 "KeyWrapTableV1 header_length must be 96"
2791 ))
2792 );
2793 }
2794
2795 #[test]
2796 fn key_wrap_records_reject_invalid_digest_and_record_length() {
2797 let table = recipient_record_bytes();
2798 let (record, _) = RecipientRecordV1::parse(&table).unwrap();
2799 assert_eq!(record.recipient_identity_length as usize, b"alice".len());
2800
2801 let mut bad_digest = table.clone();
2802 bad_digest[20] ^= 1;
2803 assert_eq!(
2804 RecipientRecordV1::parse(&bad_digest).unwrap_err(),
2805 FormatError::InvalidArchive("RecipientRecordV1 recipient_identity_digest mismatch")
2806 );
2807
2808 let mut bad_len = table.clone();
2809 write_u32(&mut bad_len, 0, 12);
2810 assert_eq!(
2811 RecipientRecordV1::parse(&bad_len).unwrap_err(),
2812 FormatError::InvalidArchive("RecipientRecordV1 record_length is too small")
2813 );
2814 }
2815
2816 #[test]
2817 fn key_wrap_records_parse_unknown_profile_id() {
2818 let table = recipient_record_bytes_with_profile_and_payload(0xBEEF, b"charlie", b"payload");
2819 let (record, consumed) = RecipientRecordV1::parse(&table).unwrap();
2820 assert_eq!(record.profile_id, 0xBEEF);
2821 assert_eq!(record.recipient_identity_bytes, b"charlie");
2822 assert_eq!(record.profile_payload_bytes, b"payload");
2823 assert_eq!(consumed, table.len());
2824 }
2825
2826 #[test]
2827 fn crypto_header_fixed_validates_v43_protection_mode_pairs() {
2828 let mut header = crypto_fixed();
2829 header.aead_algo = AeadAlgo::None;
2830 header.kdf_algo = KdfAlgo::None;
2831 header.validate_supported_profile().unwrap();
2832
2833 let mut header = crypto_fixed();
2834 header.aead_algo = AeadAlgo::None;
2835 header.kdf_algo = KdfAlgo::Raw;
2836 assert_eq!(
2837 header.validate_supported_profile().unwrap_err(),
2838 FormatError::InvalidProtectionMode {
2839 aead_algo: AeadAlgo::None,
2840 kdf_algo: KdfAlgo::Raw,
2841 }
2842 );
2843
2844 let mut header = crypto_fixed();
2845 header.aead_algo = AeadAlgo::AesGcmSiv256;
2846 header.kdf_algo = KdfAlgo::None;
2847 assert_eq!(
2848 header.validate_supported_profile().unwrap_err(),
2849 FormatError::InvalidProtectionMode {
2850 aead_algo: AeadAlgo::AesGcmSiv256,
2851 kdf_algo: KdfAlgo::None,
2852 }
2853 );
2854
2855 let mut header = crypto_fixed();
2856 header.aead_algo = AeadAlgo::None;
2857 header.kdf_algo = KdfAlgo::RecipientWrap;
2858 assert_eq!(
2859 header.validate_supported_profile().unwrap_err(),
2860 FormatError::InvalidProtectionMode {
2861 aead_algo: AeadAlgo::None,
2862 kdf_algo: KdfAlgo::RecipientWrap,
2863 }
2864 );
2865
2866 let mut header = crypto_fixed();
2867 header.aead_algo = AeadAlgo::AesGcmSiv256;
2868 header.kdf_algo = KdfAlgo::RecipientWrap;
2869 header.validate_supported_profile().unwrap();
2870 }
2871
2872 #[test]
2873 fn crypto_header_fixed_rejects_parameter_mutation_matrix() {
2874 let mut bytes = crypto_fixed().to_bytes();
2875 write_u16(&mut bytes, 8, 99);
2876 assert_eq!(
2877 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2878 FormatError::UnknownCompressionAlgo(99)
2879 );
2880
2881 let mut bytes = crypto_fixed().to_bytes();
2882 write_u16(&mut bytes, 10, 99);
2883 assert_eq!(
2884 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2885 FormatError::UnknownAeadAlgo(99)
2886 );
2887
2888 let mut bytes = crypto_fixed().to_bytes();
2889 write_u16(&mut bytes, 12, 99);
2890 assert_eq!(
2891 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2892 FormatError::UnknownFecAlgo(99)
2893 );
2894
2895 let mut bytes = crypto_fixed().to_bytes();
2896 write_u16(&mut bytes, 14, 99);
2897 assert_eq!(
2898 CryptoHeaderFixed::parse(&bytes, crypto_fixed().length).unwrap_err(),
2899 FormatError::UnknownKdfAlgo(99)
2900 );
2901
2902 let cases: Vec<(&'static str, CryptoHeaderFixed, FormatError)> = vec![
2903 (
2904 "unsupported FEC None",
2905 CryptoHeaderFixed {
2906 fec_algo: FecAlgo::None,
2907 ..crypto_fixed()
2908 },
2909 FormatError::UnsupportedFec(FecAlgo::None),
2910 ),
2911 (
2912 "unsupported FEC Wirehair",
2913 CryptoHeaderFixed {
2914 fec_algo: FecAlgo::Wirehair,
2915 ..crypto_fixed()
2916 },
2917 FormatError::UnsupportedFec(FecAlgo::Wirehair),
2918 ),
2919 (
2920 "invalid dictionary flag",
2921 CryptoHeaderFixed {
2922 has_dictionary: 2,
2923 ..crypto_fixed()
2924 },
2925 FormatError::InvalidBoolean {
2926 field: "has_dictionary",
2927 value: 2,
2928 },
2929 ),
2930 (
2931 "zero stripe width",
2932 CryptoHeaderFixed {
2933 stripe_width: 0,
2934 ..crypto_fixed()
2935 },
2936 FormatError::ZeroStripeWidth,
2937 ),
2938 (
2939 "stripe width cap",
2940 CryptoHeaderFixed {
2941 stripe_width: READER_MAX_STRIPE_WIDTH + 1,
2942 ..crypto_fixed()
2943 },
2944 FormatError::ReaderResourceLimitExceeded {
2945 field: "stripe_width",
2946 cap: READER_MAX_STRIPE_WIDTH as u64,
2947 actual: (READER_MAX_STRIPE_WIDTH + 1) as u64,
2948 },
2949 ),
2950 (
2951 "loss tolerance must be below stripe width",
2952 CryptoHeaderFixed {
2953 stripe_width: 2,
2954 volume_loss_tolerance: 2,
2955 ..crypto_fixed()
2956 },
2957 FormatError::VolumeLossToleranceOutOfRange {
2958 volume_loss_tolerance: 2,
2959 stripe_width: 2,
2960 },
2961 ),
2962 (
2963 "bit rot pct cap",
2964 CryptoHeaderFixed {
2965 bit_rot_buffer_pct: 101,
2966 ..crypto_fixed()
2967 },
2968 FormatError::BitRotBufferPctTooLarge(101),
2969 ),
2970 (
2971 "zero payload data shards",
2972 CryptoHeaderFixed {
2973 fec_data_shards: 0,
2974 ..crypto_fixed()
2975 },
2976 FormatError::ZeroDataShardMaximum {
2977 field: "fec_data_shards",
2978 },
2979 ),
2980 (
2981 "zero index data shards",
2982 CryptoHeaderFixed {
2983 index_fec_data_shards: 0,
2984 ..crypto_fixed()
2985 },
2986 FormatError::ZeroDataShardMaximum {
2987 field: "index_fec_data_shards",
2988 },
2989 ),
2990 (
2991 "zero index root data shards",
2992 CryptoHeaderFixed {
2993 index_root_fec_data_shards: 0,
2994 ..crypto_fixed()
2995 },
2996 FormatError::ZeroDataShardMaximum {
2997 field: "index_root_fec_data_shards",
2998 },
2999 ),
3000 (
3001 "zero chunk size",
3002 CryptoHeaderFixed {
3003 chunk_size: 0,
3004 ..crypto_fixed()
3005 },
3006 FormatError::ZeroChunkSize,
3007 ),
3008 (
3009 "zero envelope target size",
3010 CryptoHeaderFixed {
3011 envelope_target_size: 0,
3012 ..crypto_fixed()
3013 },
3014 FormatError::ZeroEnvelopeTargetSize,
3015 ),
3016 (
3017 "chunk exceeds envelope",
3018 CryptoHeaderFixed {
3019 chunk_size: 4096,
3020 envelope_target_size: 2048,
3021 ..crypto_fixed()
3022 },
3023 FormatError::ChunkSizeExceedsEnvelopeTarget {
3024 chunk_size: 4096,
3025 envelope_target_size: 2048,
3026 },
3027 ),
3028 (
3029 "block size too small",
3030 CryptoHeaderFixed {
3031 block_size: 2048,
3032 ..crypto_fixed()
3033 },
3034 FormatError::BlockSizeTooSmall(2048),
3035 ),
3036 ];
3037
3038 for (name, header, expected) in cases {
3039 assert_eq!(
3040 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3041 expected,
3042 "{name}"
3043 );
3044 }
3045 }
3046
3047 #[test]
3048 fn crypto_header_fixed_treats_expected_volume_size_as_advisory_not_reserved() {
3049 let mut header = crypto_fixed();
3050 header.expected_volume_size = 1u64 << 56;
3051
3052 let parsed = CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap();
3053
3054 assert_eq!(parsed.expected_volume_size, 1u64 << 56);
3055 }
3056
3057 #[test]
3058 fn crypto_header_fixed_rejects_reader_cap_excesses() {
3059 let mut header = crypto_fixed();
3060 header.chunk_size = READER_MAX_CHUNK_SIZE + 1;
3061 header.envelope_target_size = header.chunk_size;
3062 assert_eq!(
3063 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3064 FormatError::ReaderResourceLimitExceeded {
3065 field: "chunk_size",
3066 cap: READER_MAX_CHUNK_SIZE as u64,
3067 actual: (READER_MAX_CHUNK_SIZE + 1) as u64,
3068 }
3069 );
3070
3071 let mut header = crypto_fixed();
3072 header.block_size = READER_MAX_BLOCK_SIZE + 2;
3073 assert_eq!(
3074 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3075 FormatError::ReaderResourceLimitExceeded {
3076 field: "block_size",
3077 cap: READER_MAX_BLOCK_SIZE as u64,
3078 actual: (READER_MAX_BLOCK_SIZE + 2) as u64,
3079 }
3080 );
3081
3082 let mut header = crypto_fixed();
3083 header.max_path_length = READER_MAX_PATH_LENGTH + 1;
3084 assert_eq!(
3085 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3086 FormatError::ReaderResourceLimitExceeded {
3087 field: "max_path_length",
3088 cap: READER_MAX_PATH_LENGTH as u64,
3089 actual: (READER_MAX_PATH_LENGTH + 1) as u64,
3090 }
3091 );
3092
3093 let mut header = crypto_fixed();
3094 header.fec_data_shards = READER_MAX_FEC_CLASS_SHARDS as u16;
3095 header.fec_parity_shards = 1;
3096 assert_eq!(
3097 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3098 FormatError::ReaderResourceLimitExceeded {
3099 field: "fec_data_shards + fec_parity_shards",
3100 cap: READER_MAX_FEC_CLASS_SHARDS as u64,
3101 actual: (READER_MAX_FEC_CLASS_SHARDS + 1) as u64,
3102 }
3103 );
3104
3105 let mut header = crypto_fixed();
3106 header.index_fec_data_shards = READER_MAX_INDEX_FEC_CLASS_SHARDS as u16;
3107 header.index_fec_parity_shards = 1;
3108 assert_eq!(
3109 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3110 FormatError::ReaderResourceLimitExceeded {
3111 field: "index_fec_data_shards + index_fec_parity_shards",
3112 cap: READER_MAX_INDEX_FEC_CLASS_SHARDS as u64,
3113 actual: (READER_MAX_INDEX_FEC_CLASS_SHARDS + 1) as u64,
3114 }
3115 );
3116
3117 let mut header = crypto_fixed();
3118 header.block_size = 1_048_576;
3119 header.fec_data_shards = 4_096;
3120 header.fec_parity_shards = 0;
3121 let max_data_shards = u32::MAX as u64 / header.block_size as u64;
3122 assert_eq!(
3123 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3124 FormatError::ReaderResourceLimitExceeded {
3125 field: "fec_data_shards",
3126 cap: max_data_shards,
3127 actual: 4_096,
3128 }
3129 );
3130
3131 let mut header = crypto_fixed();
3132 header.block_size = 1_048_576;
3133 header.index_fec_data_shards = 4_096;
3134 header.index_fec_parity_shards = 0;
3135 let max_data_shards = u32::MAX as u64 / header.block_size as u64;
3136 assert_eq!(
3137 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3138 FormatError::ReaderResourceLimitExceeded {
3139 field: "index_fec_data_shards",
3140 cap: max_data_shards,
3141 actual: 4_096,
3142 }
3143 );
3144
3145 let mut header = crypto_fixed();
3146 header.block_size = 1_048_576;
3147 header.index_root_fec_data_shards = 4_096;
3148 let max_data_shards = u32::MAX as u64 / header.block_size as u64;
3149 assert_eq!(
3150 CryptoHeaderFixed::parse(&header.to_bytes(), header.length).unwrap_err(),
3151 FormatError::ReaderResourceLimitExceeded {
3152 field: "index_root_fec_data_shards",
3153 cap: max_data_shards,
3154 actual: 4_096,
3155 }
3156 );
3157 }
3158
3159 #[test]
3160 fn crypto_extension_scanner_enforces_terminator_and_caps() {
3161 let bytes = [0u8; CRYPTO_EXTENSION_HEADER_LEN];
3162 assert!(scan_crypto_extension_tlvs(&bytes).unwrap().is_empty());
3163
3164 let mut bytes = Vec::new();
3165 bytes.extend_from_slice(&0x0001u16.to_le_bytes());
3166 bytes.extend_from_slice(&3u32.to_le_bytes());
3167 bytes.extend_from_slice(b"hey");
3168 bytes.extend_from_slice(&0u16.to_le_bytes());
3169 bytes.extend_from_slice(&0u32.to_le_bytes());
3170 let tlvs = scan_crypto_extension_tlvs(&bytes).unwrap();
3171 assert_eq!(tlvs[0].tag, 1);
3172 assert_eq!(tlvs[0].value, b"hey");
3173
3174 let mut bytes = Vec::new();
3175 bytes.extend_from_slice(&0x0001u16.to_le_bytes());
3176 bytes.extend_from_slice(&257u32.to_le_bytes());
3177 assert_eq!(
3178 scan_crypto_extension_tlvs(&bytes).unwrap_err(),
3179 FormatError::ExtensionPayloadTooLarge(257)
3180 );
3181
3182 assert_eq!(
3183 scan_crypto_extension_tlvs(&[]).unwrap_err(),
3184 FormatError::MissingExtensionTerminator
3185 );
3186 }
3187
3188 #[test]
3189 fn crypto_header_parse_splits_fixed_kdf_extensions_and_hmac() {
3190 let bytes = raw_crypto_header_bytes();
3191 let header = CryptoHeader::parse(&bytes, bytes.len() as u32).unwrap();
3192 assert_eq!(header.fixed.kdf_algo, KdfAlgo::Raw);
3193 assert_eq!(header.kdf_params, KdfParams::Raw);
3194 assert!(header.extensions.is_empty());
3195 assert_eq!(header.header_hmac, [0xab; CRYPTO_HEADER_HMAC_LEN]);
3196 assert_eq!(
3197 header.hmac_covered_bytes.len(),
3198 bytes.len() - CRYPTO_HEADER_HMAC_LEN
3199 );
3200 }
3201
3202 #[test]
3203 fn crypto_header_parse_rejects_truncated_and_bad_kdf_params() {
3204 let mut bytes = raw_crypto_header_bytes();
3205 bytes.truncate(CRYPTO_HEADER_FIXED_LEN + 1);
3206 assert_eq!(
3207 CryptoHeader::parse(&bytes, bytes.len() as u32).unwrap_err(),
3208 FormatError::CryptoHeaderTooShort {
3209 min: CRYPTO_HEADER_FIXED_LEN
3210 + 2
3211 + CRYPTO_EXTENSION_HEADER_LEN
3212 + CRYPTO_HEADER_HMAC_LEN,
3213 actual: CRYPTO_HEADER_FIXED_LEN + 1
3214 }
3215 );
3216
3217 let mut bytes = raw_crypto_header_bytes();
3218 write_u16(
3219 &mut bytes,
3220 CRYPTO_HEADER_FIXED_LEN,
3221 KdfAlgo::Argon2id as u16,
3222 );
3223 assert_eq!(
3224 CryptoHeader::parse(&bytes, bytes.len() as u32).unwrap_err(),
3225 FormatError::KdfAlgoTagMismatch {
3226 expected: 0,
3227 actual: 1
3228 }
3229 );
3230 }
3231
3232 #[test]
3233 fn crypto_header_length_mismatch_matrix_rejects_independent_declared_lengths() {
3234 let canonical = raw_crypto_header_bytes();
3235 CryptoHeader::parse(&canonical, canonical.len() as u32).unwrap();
3236
3237 let mut fixed_longer = canonical.clone();
3238 write_u32(&mut fixed_longer, 4, (canonical.len() + 1) as u32);
3239 assert_eq!(
3240 CryptoHeader::parse(&fixed_longer, fixed_longer.len() as u32).unwrap_err(),
3241 FormatError::CryptoHeaderLengthMismatch {
3242 fixed: (canonical.len() + 1) as u32,
3243 volume: canonical.len() as u32,
3244 }
3245 );
3246
3247 let longer_volume_len = (canonical.len() + 1) as u32;
3248 let mut padded = canonical.clone();
3249 padded.insert(canonical.len() - CRYPTO_HEADER_HMAC_LEN, 0);
3250 assert_eq!(
3251 CryptoHeader::parse(&padded, longer_volume_len).unwrap_err(),
3252 FormatError::CryptoHeaderLengthMismatch {
3253 fixed: canonical.len() as u32,
3254 volume: longer_volume_len,
3255 }
3256 );
3257
3258 let mut fixed_shorter = canonical.clone();
3259 write_u32(&mut fixed_shorter, 4, (canonical.len() - 1) as u32);
3260 assert_eq!(
3261 CryptoHeader::parse(&fixed_shorter, fixed_shorter.len() as u32).unwrap_err(),
3262 FormatError::CryptoHeaderLengthMismatch {
3263 fixed: (canonical.len() - 1) as u32,
3264 volume: canonical.len() as u32,
3265 }
3266 );
3267 }
3268
3269 #[test]
3270 fn crypto_extension_semantics_reject_forbidden_duplicate_and_critical() {
3271 let duplicate = vec![
3272 ExtensionTlv {
3273 tag: 0x0001,
3274 value: b"one",
3275 },
3276 ExtensionTlv {
3277 tag: 0x0001,
3278 value: b"two",
3279 },
3280 ];
3281 assert_eq!(
3282 validate_crypto_extension_semantics(&duplicate).unwrap_err(),
3283 FormatError::DuplicateKnownExtension(0x0001)
3284 );
3285
3286 let forbidden = vec![ExtensionTlv {
3287 tag: 0x8004,
3288 value: b"",
3289 }];
3290 assert_eq!(
3291 validate_crypto_extension_semantics(&forbidden).unwrap_err(),
3292 FormatError::ForbiddenExtensionTag(0x0004)
3293 );
3294
3295 let unknown_critical = vec![ExtensionTlv {
3296 tag: 0x8123,
3297 value: b"",
3298 }];
3299 assert_eq!(
3300 validate_crypto_extension_semantics(&unknown_critical).unwrap_err(),
3301 FormatError::UnknownCriticalExtension(0x0123)
3302 );
3303
3304 let malformed_known = vec![ExtensionTlv {
3305 tag: 0x0003,
3306 value: b"short",
3307 }];
3308 assert_eq!(
3309 validate_crypto_extension_semantics(&malformed_known).unwrap_err(),
3310 FormatError::MalformedKnownExtension(0x0003)
3311 );
3312 }
3313
3314 #[test]
3315 fn block_record_round_trips_and_validates_crc() {
3316 let record = BlockRecord {
3317 block_index: 0,
3318 kind: BlockKind::PayloadData,
3319 flags: BLOCK_LAST_DATA_FLAG,
3320 payload: vec![7; 4096],
3321 record_crc32c: 0,
3322 };
3323 let bytes = record.to_bytes();
3324 let parsed = BlockRecord::parse(&bytes, 4096).unwrap();
3325 assert_eq!(parsed.kind, BlockKind::PayloadData);
3326 assert!(parsed.is_last_data());
3327
3328 let mut corrupted = bytes;
3329 corrupted[20] ^= 1;
3330 assert_eq!(
3331 BlockRecord::parse(&corrupted, 4096).unwrap_err(),
3332 FormatError::BadCrc {
3333 structure: "BlockRecord"
3334 }
3335 );
3336 }
3337
3338 #[test]
3339 fn block_record_crc_covers_every_record_header_and_payload_byte() {
3340 let record = BlockRecord {
3341 block_index: 0x0102_0304_0506_0708,
3342 kind: BlockKind::PayloadData,
3343 flags: BLOCK_LAST_DATA_FLAG,
3344 payload: (0..4096).map(|idx| (idx & 0xff) as u8).collect(),
3345 record_crc32c: 0,
3346 };
3347 let bytes = record.to_bytes();
3348 let covered_len = 16 + record.payload.len();
3349
3350 for offset in 0..covered_len {
3351 let mut corrupted = bytes.clone();
3352 corrupted[offset] ^= 0x80;
3353 let err = BlockRecord::parse(&corrupted, record.payload.len()).unwrap_err();
3354 if offset < 4 {
3355 assert_eq!(
3356 err,
3357 FormatError::BadMagic {
3358 structure: "BlockRecord"
3359 },
3360 "magic byte {offset}"
3361 );
3362 } else if (14..16).contains(&offset) {
3363 assert_eq!(
3364 err,
3365 FormatError::NonZeroReserved {
3366 structure: "BlockRecord"
3367 },
3368 "reserved byte {offset}"
3369 );
3370 } else {
3371 assert_eq!(
3372 err,
3373 FormatError::BadCrc {
3374 structure: "BlockRecord"
3375 },
3376 "CRC-covered byte {offset}"
3377 );
3378 }
3379 }
3380
3381 let mut corrupted_crc = bytes;
3382 corrupted_crc[covered_len] ^= 0x80;
3383 assert_eq!(
3384 BlockRecord::parse(&corrupted_crc, record.payload.len()).unwrap_err(),
3385 FormatError::BadCrc {
3386 structure: "BlockRecord"
3387 }
3388 );
3389 }
3390
3391 #[test]
3392 fn block_record_rejects_reserved_kind_flags_and_parity_last_flag() {
3393 let mut record = BlockRecord {
3394 block_index: 0,
3395 kind: BlockKind::PayloadData,
3396 flags: 0x02,
3397 payload: vec![0; 4096],
3398 record_crc32c: 0,
3399 };
3400 assert_eq!(
3401 BlockRecord::parse(&record.to_bytes(), 4096).unwrap_err(),
3402 FormatError::InvalidBlockFlags(0x02)
3403 );
3404
3405 record.kind = BlockKind::PayloadParity;
3406 record.flags = BLOCK_LAST_DATA_FLAG;
3407 assert_eq!(
3408 BlockRecord::parse(&record.to_bytes(), 4096).unwrap_err(),
3409 FormatError::ParityBlockHasLastDataFlag
3410 );
3411
3412 let mut bytes = record.to_bytes();
3413 bytes[12] = 10;
3414 let crc = crc32c(&bytes[..4112]);
3415 write_u32(&mut bytes, 4112, crc);
3416 assert_eq!(
3417 BlockRecord::parse(&bytes, 4096).unwrap_err(),
3418 FormatError::UnknownBlockKind(10)
3419 );
3420
3421 for unknown_kind in 10..=u8::MAX {
3422 let mut bytes = record.to_bytes();
3423 bytes[12] = unknown_kind;
3424 let crc = crc32c(&bytes[..4112]);
3425 write_u32(&mut bytes, 4112, crc);
3426 assert_eq!(
3427 BlockRecord::parse(&bytes, 4096).unwrap_err(),
3428 FormatError::UnknownBlockKind(unknown_kind)
3429 );
3430 }
3431 }
3432
3433 #[test]
3434 fn manifest_footer_round_trips_and_validates_index_extent() {
3435 let footer = ManifestFooter {
3436 archive_uuid: uuid(),
3437 session_id: session(),
3438 volume_index: 0,
3439 is_authoritative: 1,
3440 total_volumes: 1,
3441 index_root_first_block: 0,
3442 index_root_data_block_count: 2,
3443 index_root_parity_block_count: 1,
3444 index_root_encrypted_size: 8192,
3445 index_root_decompressed_size: 120,
3446 manifest_hmac: [0xaa; 32],
3447 };
3448 let parsed = ManifestFooter::parse(&footer.to_bytes()).unwrap();
3449 parsed.validate_index_root_extent(4096).unwrap();
3450
3451 let mut bad = footer.clone();
3452 bad.index_root_encrypted_size = 4096;
3453 assert_eq!(
3454 ManifestFooter::parse(&bad.to_bytes())
3455 .unwrap()
3456 .validate_index_root_extent(4096)
3457 .unwrap_err(),
3458 FormatError::IndexRootSizeMismatch
3459 );
3460 }
3461
3462 #[test]
3463 fn volume_trailer_round_trips_and_requires_manifest_length() {
3464 let trailer = VolumeTrailer {
3465 archive_uuid: uuid(),
3466 session_id: session(),
3467 volume_index: 0,
3468 block_count: 3,
3469 bytes_written: 10_000,
3470 manifest_footer_offset: 9_864,
3471 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
3472 closed_at_ns: 123,
3473 root_auth_footer_offset: 0,
3474 root_auth_footer_length: 0,
3475 root_auth_flags: 0,
3476 trailer_hmac: [0xbb; 32],
3477 };
3478 let parsed = VolumeTrailer::parse(&trailer.to_bytes()).unwrap();
3479 assert_eq!(parsed.block_count, 3);
3480
3481 let mut bad = trailer;
3482 bad.manifest_footer_length = 100;
3483 assert_eq!(
3484 VolumeTrailer::parse(&bad.to_bytes()).unwrap_err(),
3485 FormatError::InvalidManifestFooterLength(100)
3486 );
3487 }
3488
3489 #[test]
3490 fn root_auth_footer_round_trips_and_validates_crc_and_lengths() {
3491 let footer = RootAuthFooterV1 {
3492 archive_uuid: uuid(),
3493 session_id: session(),
3494 format_version: FORMAT_VERSION,
3495 volume_format_rev: VOLUME_FORMAT_REV,
3496 authenticator_id: 2,
3497 signer_identity_type: 1,
3498 signer_identity_bytes: b"public-key".to_vec(),
3499 authenticator_value: vec![0x5a; 68],
3500 total_data_block_count: 7,
3501 critical_metadata_digest: [1; 32],
3502 index_digest: [2; 32],
3503 fec_layout_digest: [3; 32],
3504 data_block_merkle_root: [4; 32],
3505 signer_identity_digest: [5; 32],
3506 archive_root: [6; 32],
3507 footer_crc32c: 0,
3508 };
3509 let bytes = footer.to_bytes().unwrap();
3510 let parsed = RootAuthFooterV1::parse(&bytes).unwrap();
3511 assert_eq!(parsed.archive_uuid, uuid());
3512 assert_eq!(parsed.signer_identity_bytes, b"public-key");
3513 assert_eq!(parsed.authenticator_value, vec![0x5a; 68]);
3514 assert_eq!(parsed.footer_length().unwrap() as usize, bytes.len());
3515
3516 let mut bad_crc = bytes.clone();
3517 bad_crc[100] ^= 0x40;
3518 assert_eq!(
3519 RootAuthFooterV1::parse(&bad_crc).unwrap_err(),
3520 FormatError::BadCrc {
3521 structure: "RootAuthFooterV1"
3522 }
3523 );
3524
3525 let mut bad_len = bytes;
3526 write_u32(&mut bad_len, 30, 1);
3527 let crc_offset = bad_len.len() - 4;
3528 let crc = crc32c(&bad_len[..crc_offset]);
3529 write_u32(&mut bad_len, crc_offset, crc);
3530 assert!(matches!(
3531 RootAuthFooterV1::parse(&bad_len).unwrap_err(),
3532 FormatError::InvalidLength {
3533 structure: "RootAuthFooterV1",
3534 ..
3535 }
3536 ));
3537
3538 let mut v43 = footer.to_bytes().unwrap();
3539 v43[6..30].copy_from_slice(&crate::format::ROOT_AUTH_SPEC_ID_V43);
3540 write_u16(&mut v43, 72, VOLUME_FORMAT_REV_43);
3541 let v43_crc_offset = v43.len() - 4;
3542 let v43_crc = crc32c(&v43[..v43_crc_offset]);
3543 write_u32(&mut v43, v43_crc_offset, v43_crc);
3544 let parsed_v43 = RootAuthFooterV1::parse(&v43).unwrap();
3545 assert_eq!(parsed_v43.volume_format_rev, VOLUME_FORMAT_REV_43);
3546
3547 let mut mismatched = footer.to_bytes().unwrap();
3548 write_u16(&mut mismatched, 72, VOLUME_FORMAT_REV_43);
3549 let mismatch_crc_offset = mismatched.len() - 4;
3550 let mismatch_crc = crc32c(&mismatched[..mismatch_crc_offset]);
3551 write_u32(&mut mismatched, mismatch_crc_offset, mismatch_crc);
3552 assert_eq!(
3553 RootAuthFooterV1::parse(&mismatched).unwrap_err(),
3554 FormatError::InvalidArchive(
3555 "RootAuthFooterV1 root_auth_spec_id does not match volume_format_rev",
3556 )
3557 );
3558 }
3559
3560 #[test]
3561 fn bootstrap_sidecar_header_validates_presence_and_layout() {
3562 let header = BootstrapSidecarHeader {
3563 archive_uuid: uuid(),
3564 session_id: session(),
3565 flags: SIDECAR_MANIFEST_PRESENT | SIDECAR_INDEX_ROOT_PRESENT,
3566 manifest_footer_offset: BOOTSTRAP_SIDECAR_HEADER_LEN as u64,
3567 manifest_footer_length: MANIFEST_FOOTER_LEN as u32,
3568 index_root_records_offset: (BOOTSTRAP_SIDECAR_HEADER_LEN + MANIFEST_FOOTER_LEN) as u64,
3569 index_root_records_length: 40,
3570 dictionary_records_offset: 0,
3571 dictionary_records_length: 0,
3572 sidecar_hmac: [0xcc; 32],
3573 header_crc32c: 0,
3574 };
3575 let parsed = BootstrapSidecarHeader::parse(&header.to_bytes()).unwrap();
3576 parsed
3577 .validate_packed_layout(
3578 BOOTSTRAP_SIDECAR_HEADER_LEN as u64 + MANIFEST_FOOTER_LEN as u64 + 40,
3579 )
3580 .unwrap();
3581
3582 let mut bad = header.clone();
3583 bad.flags |= 0x08;
3584 assert_eq!(
3585 BootstrapSidecarHeader::parse(&bad.to_bytes()).unwrap_err(),
3586 FormatError::UnknownBootstrapSidecarFlags(0x0b)
3587 );
3588
3589 let mut bad = header;
3590 bad.index_root_records_offset += 1;
3591 let parsed = BootstrapSidecarHeader::parse(&bad.to_bytes()).unwrap();
3592 assert_eq!(
3593 parsed
3594 .validate_packed_layout(
3595 BOOTSTRAP_SIDECAR_HEADER_LEN as u64 + MANIFEST_FOOTER_LEN as u64 + 40
3596 )
3597 .unwrap_err(),
3598 FormatError::NonCanonicalBootstrapSidecarLayout
3599 );
3600 }
3601
3602 #[test]
3603 fn critical_metadata_image_rejects_future_volume_format_revision() {
3604 let image = CriticalMetadataImage {
3605 volume_format_rev: VOLUME_FORMAT_REV_43,
3606 archive_uuid: uuid(),
3607 session_id: session(),
3608 volume_index: 0,
3609 stripe_width: 1,
3610 layout_flags: 1,
3611 volume_header_offset: 0,
3612 volume_header_length: VOLUME_HEADER_LEN as u32,
3613 crypto_header_offset: 0,
3614 crypto_header_length: 0,
3615 key_wrap_table_offset: 0,
3616 key_wrap_table_length: 0,
3617 block_records_offset: 0,
3618 block_records_length: 0,
3619 block_count: 0,
3620 manifest_footer_offset: 0,
3621 manifest_footer_length: 0,
3622 root_auth_footer_offset: 0,
3623 root_auth_footer_length: 0,
3624 volume_trailer_offset: 0,
3625 volume_trailer_length: 0,
3626 body_bytes_before_cmra: 0,
3627 volume_header_sha256: [0u8; 32],
3628 crypto_header_sha256: [0u8; 32],
3629 key_wrap_table_sha256: [0u8; 32],
3630 manifest_footer_sha256: [0u8; 32],
3631 root_auth_footer_sha256: [0u8; 32],
3632 volume_trailer_sha256: [0u8; 32],
3633 regions: vec![],
3634 };
3635 let mut bytes = image.to_bytes().unwrap();
3636 write_u16(&mut bytes, 6, VOLUME_FORMAT_REV_44 + 1);
3637 let crc_offset = bytes.len() - 4;
3638 let crc = crc32c(&bytes[..crc_offset]);
3639 write_u32(&mut bytes, crc_offset, crc);
3640 assert_eq!(
3641 CriticalMetadataImage::parse(&bytes).unwrap_err(),
3642 FormatError::UnsupportedVolumeFormatRevision {
3643 format_version: FORMAT_VERSION,
3644 volume_format_rev: VOLUME_FORMAT_REV_44 + 1,
3645 reader_max_supported_revision: READER_MAX_SUPPORTED_VOLUME_FORMAT_REV,
3646 }
3647 );
3648 }
3649}