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