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