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