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