Skip to main content

tzap_core/
wire.rs

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