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