Skip to main content

tzap_core/
wire.rs

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