Skip to main content

tzap_core/
non_seekable_reader.rs

1use std::collections::BTreeMap;
2use std::fs;
3use std::io::{ErrorKind, Read};
4use std::path::{Path, PathBuf};
5
6use sha2::{Digest, Sha256};
7
8use crate::compression::validate_exact_zstd_frame;
9use crate::crypto::{
10    decrypt_padded_aead_object, verify_integrity_tag, AeadObjectContext, HmacDomain, MasterKey,
11    Subkeys,
12};
13use crate::fec::repair_data_gf16;
14use crate::format::{
15    BlockKind, ExtractError, FormatError, BLOCK_RECORD_FRAMING_LEN, VOLUME_HEADER_LEN,
16};
17use crate::raw_stream_profile::reject_unsupported_raw_stream_profile;
18use crate::reader::{
19    block_record_error_is_recoverable_erasure, expected_stream_block_index,
20    manifest_bootstrap_fields_match, observed_archive_size, parse_non_seekable_bootstrap_material,
21    parse_terminal_material_read_at, recipient_wrap_subkeys_from_table, required_object_parity,
22    startup_key_wrap_table, total_extraction_size_cap, v41_terminal_tail_cap,
23    validate_crypto_class_parity_exactness, validate_reader_options, ArchiveEntry, ArchiveReadAt,
24    KeyHoldingTerminalContext, NonSeekableBootstrapMaterial, OpenedArchive, ReaderOptions,
25    RecipientWrapCandidateMasterKey, RecipientWrapRecordContext, StreamedArchiveOpenParts,
26};
27use crate::tar_model::{
28    NoopTarStreamObserver, SafeExtractionOptions, TarStreamFilesystemRestoreObserver,
29    TarStreamMemberSummary, TarStreamObserver, TarStreamSummary, TarStreamSummaryValidator,
30};
31use crate::wire::{
32    BlockRecord, CryptoHeader, CryptoHeaderFixed, ExtensionTlv, RootAuthFooterV1, VolumeHeader,
33};
34
35const DEFAULT_MAX_RETAINED_METADATA_BYTES: usize = 128 * 1024 * 1024;
36const DEFAULT_MAX_INCOMPLETE_TAR_GROUP_BYTES: usize = 1024 * 1024;
37const DEFAULT_MAX_STREAMED_MEMBER_COUNT: u64 = 1_000_000;
38
39fn parse_volume_format_dispatch(volume_header: &VolumeHeader) -> Result<(), FormatError> {
40    let revision = volume_header.parse_volume_format_revision()?;
41    match revision {
42        crate::format::VolumeFormatRevision::V43 | crate::format::VolumeFormatRevision::V44 => {
43            Ok(())
44        }
45    }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum SequentialRootAuthStatus {
50    Absent,
51    WireValidOnly,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct SequentialVerifyReport {
56    pub archive_uuid: [u8; 16],
57    pub session_id: [u8; 16],
58    pub volume_format_rev: u16,
59    pub volume_index: u32,
60    pub total_volumes: u32,
61    pub file_count: u64,
62    pub payload_block_count: u64,
63    pub tar_total_size: u64,
64    pub content_sha256: [u8; 32],
65    pub root_auth: SequentialRootAuthStatus,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct SequentialExtractReport {
70    pub verification: SequentialVerifyReport,
71    pub extracted_member_count: u64,
72    pub degraded_metadata_count: u64,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct SequentialListReport {
77    pub verification: SequentialVerifyReport,
78    pub entries: Vec<ArchiveEntry>,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct NonSeekableReaderOptions {
83    pub reader: ReaderOptions,
84    pub max_terminal_tail_size: usize,
85    pub max_retained_metadata_bytes: usize,
86    pub max_incomplete_tar_group_bytes: usize,
87    pub max_streamed_member_count: u64,
88}
89
90impl Default for NonSeekableReaderOptions {
91    fn default() -> Self {
92        Self {
93            reader: ReaderOptions::default(),
94            max_terminal_tail_size: v41_terminal_tail_cap()
95                .expect("v41 terminal tail cap must fit usize"),
96            max_retained_metadata_bytes: DEFAULT_MAX_RETAINED_METADATA_BYTES,
97            max_incomplete_tar_group_bytes: DEFAULT_MAX_INCOMPLETE_TAR_GROUP_BYTES,
98            max_streamed_member_count: DEFAULT_MAX_STREAMED_MEMBER_COUNT,
99        }
100    }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub(crate) struct StreamedEnvelopeSummary {
105    pub(crate) envelope_index: u64,
106    pub(crate) first_block_index: u64,
107    pub(crate) data_block_count: u32,
108    pub(crate) parity_block_count: u32,
109    pub(crate) encrypted_size: u32,
110    pub(crate) plaintext_size: u32,
111    pub(crate) first_frame_index: u64,
112    pub(crate) frame_count: u32,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub(crate) struct StreamedFrameSummary {
117    pub(crate) frame_index: u64,
118    pub(crate) envelope_index: u64,
119    pub(crate) offset_in_envelope: u32,
120    pub(crate) compressed_size: u32,
121    pub(crate) decompressed_size: u32,
122    pub(crate) tar_stream_offset: u64,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub(crate) struct StreamedPayloadSummary {
127    pub(crate) tar: TarStreamSummary,
128    pub(crate) content_sha256: [u8; 32],
129    pub(crate) envelopes: Vec<StreamedEnvelopeSummary>,
130    pub(crate) frames: Vec<StreamedFrameSummary>,
131}
132
133impl StreamedPayloadSummary {
134    pub(crate) fn envelope_map(
135        &self,
136    ) -> Result<BTreeMap<u64, &StreamedEnvelopeSummary>, FormatError> {
137        let mut map = BTreeMap::new();
138        for envelope in &self.envelopes {
139            if map.insert(envelope.envelope_index, envelope).is_some() {
140                return Err(FormatError::InvalidArchive(
141                    "duplicate streamed payload envelope",
142                ));
143            }
144        }
145        Ok(map)
146    }
147
148    pub(crate) fn frame_map(&self) -> Result<BTreeMap<u64, &StreamedFrameSummary>, FormatError> {
149        let mut map = BTreeMap::new();
150        for frame in &self.frames {
151            if map.insert(frame.frame_index, frame).is_some() {
152                return Err(FormatError::InvalidArchive(
153                    "duplicate streamed payload frame",
154                ));
155            }
156        }
157        Ok(map)
158    }
159
160    pub(crate) fn member_start_map(
161        &self,
162    ) -> Result<BTreeMap<u64, &TarStreamMemberSummary>, FormatError> {
163        let mut map = BTreeMap::new();
164        for member in &self.tar.members {
165            if map.insert(member.group_start, member).is_some() {
166                return Err(FormatError::InvalidArchive(
167                    "duplicate streamed tar member start",
168                ));
169            }
170        }
171        Ok(map)
172    }
173
174    pub(crate) fn frame_flags(&self, frame: &StreamedFrameSummary) -> Result<u32, FormatError> {
175        let frame_end = frame
176            .tar_stream_offset
177            .checked_add(frame.decompressed_size as u64)
178            .ok_or(FormatError::InvalidArchive("streamed frame range overflow"))?;
179        let mut flags = 0u32;
180        for member in &self.tar.members {
181            if member.group_start == frame.tar_stream_offset {
182                flags |= 0x0000_0001;
183            }
184            let member_end = member.group_start.checked_add(member.group_size).ok_or(
185                FormatError::InvalidArchive("streamed tar member range overflow"),
186            )?;
187            if member_end == frame_end {
188                flags |= 0x0000_0002;
189            }
190        }
191        Ok(flags)
192    }
193}
194
195pub fn verify_non_seekable_stream<R: Read>(
196    reader: R,
197    master_key: &MasterKey,
198) -> Result<SequentialVerifyReport, FormatError> {
199    verify_non_seekable_stream_with_options(reader, master_key, NonSeekableReaderOptions::default())
200}
201
202pub fn verify_non_seekable_stream_with_options<R: Read>(
203    reader: R,
204    master_key: &MasterKey,
205    options: NonSeekableReaderOptions,
206) -> Result<SequentialVerifyReport, FormatError> {
207    Ok(run_non_seekable_stream(
208        reader,
209        NonSeekableKeySource::MasterKey(Some(master_key)),
210        options,
211        NoopTarStreamObserver,
212        None,
213    )?
214    .verification)
215}
216
217pub fn verify_non_seekable_stream_with_recipient_wrap_resolver_options<R, F>(
218    reader: R,
219    mut resolver: F,
220    options: NonSeekableReaderOptions,
221) -> Result<SequentialVerifyReport, FormatError>
222where
223    R: Read,
224    F: FnMut(
225        RecipientWrapRecordContext<'_>,
226    ) -> Result<Vec<RecipientWrapCandidateMasterKey>, FormatError>,
227{
228    Ok(run_non_seekable_stream(
229        reader,
230        NonSeekableKeySource::RecipientWrap(&mut resolver),
231        options,
232        NoopTarStreamObserver,
233        None,
234    )?
235    .verification)
236}
237
238pub fn verify_unencrypted_non_seekable_stream_with_options<R: Read>(
239    reader: R,
240    options: NonSeekableReaderOptions,
241) -> Result<SequentialVerifyReport, FormatError> {
242    Ok(run_non_seekable_stream(
243        reader,
244        NonSeekableKeySource::MasterKey(None),
245        options,
246        NoopTarStreamObserver,
247        None,
248    )?
249    .verification)
250}
251
252pub fn verify_non_seekable_stream_with_bootstrap_sidecar<R: Read>(
253    reader: R,
254    bootstrap_sidecar: &[u8],
255    master_key: &MasterKey,
256    options: NonSeekableReaderOptions,
257) -> Result<SequentialVerifyReport, FormatError> {
258    Ok(run_non_seekable_stream(
259        reader,
260        NonSeekableKeySource::MasterKey(Some(master_key)),
261        options,
262        NoopTarStreamObserver,
263        Some(bootstrap_sidecar),
264    )?
265    .verification)
266}
267
268pub fn verify_non_seekable_stream_with_recipient_wrap_resolver_and_bootstrap_sidecar<R, F>(
269    reader: R,
270    bootstrap_sidecar: &[u8],
271    mut resolver: F,
272    options: NonSeekableReaderOptions,
273) -> Result<SequentialVerifyReport, FormatError>
274where
275    R: Read,
276    F: FnMut(
277        RecipientWrapRecordContext<'_>,
278    ) -> Result<Vec<RecipientWrapCandidateMasterKey>, FormatError>,
279{
280    Ok(run_non_seekable_stream(
281        reader,
282        NonSeekableKeySource::RecipientWrap(&mut resolver),
283        options,
284        NoopTarStreamObserver,
285        Some(bootstrap_sidecar),
286    )?
287    .verification)
288}
289
290pub fn verify_unencrypted_non_seekable_stream_with_bootstrap_sidecar<R: Read>(
291    reader: R,
292    bootstrap_sidecar: &[u8],
293    options: NonSeekableReaderOptions,
294) -> Result<SequentialVerifyReport, FormatError> {
295    Ok(run_non_seekable_stream(
296        reader,
297        NonSeekableKeySource::MasterKey(None),
298        options,
299        NoopTarStreamObserver,
300        Some(bootstrap_sidecar),
301    )?
302    .verification)
303}
304
305pub fn extract_non_seekable_stream_to_dir<R: Read>(
306    reader: R,
307    master_key: &MasterKey,
308    output_dir: &Path,
309    options: NonSeekableReaderOptions,
310    extraction: SafeExtractionOptions,
311) -> Result<SequentialExtractReport, ExtractError> {
312    let staging = StagedExtraction::new(output_dir)?;
313    let observer = TarStreamFilesystemRestoreObserver::new(
314        staging.root(),
315        SafeExtractionOptions {
316            overwrite_existing: true,
317        },
318    );
319    let outcome = run_non_seekable_stream(
320        reader,
321        NonSeekableKeySource::MasterKey(Some(master_key)),
322        options,
323        observer,
324        None,
325    )?;
326    staging.commit(extraction)?;
327    Ok(SequentialExtractReport {
328        verification: outcome.verification,
329        extracted_member_count: outcome
330            .streamed_payload
331            .tar
332            .members
333            .len()
334            .try_into()
335            .map_err(|_| FormatError::InvalidArchive("extracted member count overflow"))?,
336        degraded_metadata_count: degraded_metadata_count(&outcome.streamed_payload)?,
337    })
338}
339
340pub fn extract_non_seekable_stream_to_dir_with_recipient_wrap_resolver<R, F>(
341    reader: R,
342    mut resolver: F,
343    output_dir: &Path,
344    options: NonSeekableReaderOptions,
345    extraction: SafeExtractionOptions,
346) -> Result<SequentialExtractReport, ExtractError>
347where
348    R: Read,
349    F: FnMut(
350        RecipientWrapRecordContext<'_>,
351    ) -> Result<Vec<RecipientWrapCandidateMasterKey>, FormatError>,
352{
353    let staging = StagedExtraction::new(output_dir)?;
354    let observer = TarStreamFilesystemRestoreObserver::new(
355        staging.root(),
356        SafeExtractionOptions {
357            overwrite_existing: true,
358        },
359    );
360    let outcome = run_non_seekable_stream(
361        reader,
362        NonSeekableKeySource::RecipientWrap(&mut resolver),
363        options,
364        observer,
365        None,
366    )?;
367    staging.commit(extraction)?;
368    Ok(SequentialExtractReport {
369        verification: outcome.verification,
370        extracted_member_count: outcome
371            .streamed_payload
372            .tar
373            .members
374            .len()
375            .try_into()
376            .map_err(|_| FormatError::InvalidArchive("extracted member count overflow"))?,
377        degraded_metadata_count: degraded_metadata_count(&outcome.streamed_payload)?,
378    })
379}
380
381pub fn extract_unencrypted_non_seekable_stream_to_dir<R: Read>(
382    reader: R,
383    output_dir: &Path,
384    options: NonSeekableReaderOptions,
385    extraction: SafeExtractionOptions,
386) -> Result<SequentialExtractReport, ExtractError> {
387    let staging = StagedExtraction::new(output_dir)?;
388    let observer = TarStreamFilesystemRestoreObserver::new(
389        staging.root(),
390        SafeExtractionOptions {
391            overwrite_existing: true,
392        },
393    );
394    let outcome = run_non_seekable_stream(
395        reader,
396        NonSeekableKeySource::MasterKey(None),
397        options,
398        observer,
399        None,
400    )?;
401    staging.commit(extraction)?;
402    Ok(SequentialExtractReport {
403        verification: outcome.verification,
404        extracted_member_count: outcome
405            .streamed_payload
406            .tar
407            .members
408            .len()
409            .try_into()
410            .map_err(|_| FormatError::InvalidArchive("extracted member count overflow"))?,
411        degraded_metadata_count: degraded_metadata_count(&outcome.streamed_payload)?,
412    })
413}
414
415pub fn extract_non_seekable_stream_to_dir_with_bootstrap_sidecar<R: Read>(
416    reader: R,
417    bootstrap_sidecar: &[u8],
418    master_key: &MasterKey,
419    output_dir: &Path,
420    options: NonSeekableReaderOptions,
421    extraction: SafeExtractionOptions,
422) -> Result<SequentialExtractReport, ExtractError> {
423    let staging = StagedExtraction::new(output_dir)?;
424    let observer = TarStreamFilesystemRestoreObserver::new(
425        staging.root(),
426        SafeExtractionOptions {
427            overwrite_existing: true,
428        },
429    );
430    let outcome = run_non_seekable_stream(
431        reader,
432        NonSeekableKeySource::MasterKey(Some(master_key)),
433        options,
434        observer,
435        Some(bootstrap_sidecar),
436    )?;
437    staging.commit(extraction)?;
438    Ok(SequentialExtractReport {
439        verification: outcome.verification,
440        extracted_member_count: outcome
441            .streamed_payload
442            .tar
443            .members
444            .len()
445            .try_into()
446            .map_err(|_| FormatError::InvalidArchive("extracted member count overflow"))?,
447        degraded_metadata_count: degraded_metadata_count(&outcome.streamed_payload)?,
448    })
449}
450
451pub fn extract_non_seekable_stream_to_dir_with_recipient_wrap_resolver_and_bootstrap_sidecar<R, F>(
452    reader: R,
453    bootstrap_sidecar: &[u8],
454    mut resolver: F,
455    output_dir: &Path,
456    options: NonSeekableReaderOptions,
457    extraction: SafeExtractionOptions,
458) -> Result<SequentialExtractReport, ExtractError>
459where
460    R: Read,
461    F: FnMut(
462        RecipientWrapRecordContext<'_>,
463    ) -> Result<Vec<RecipientWrapCandidateMasterKey>, FormatError>,
464{
465    let staging = StagedExtraction::new(output_dir)?;
466    let observer = TarStreamFilesystemRestoreObserver::new(
467        staging.root(),
468        SafeExtractionOptions {
469            overwrite_existing: true,
470        },
471    );
472    let outcome = run_non_seekable_stream(
473        reader,
474        NonSeekableKeySource::RecipientWrap(&mut resolver),
475        options,
476        observer,
477        Some(bootstrap_sidecar),
478    )?;
479    staging.commit(extraction)?;
480    Ok(SequentialExtractReport {
481        verification: outcome.verification,
482        extracted_member_count: outcome
483            .streamed_payload
484            .tar
485            .members
486            .len()
487            .try_into()
488            .map_err(|_| FormatError::InvalidArchive("extracted member count overflow"))?,
489        degraded_metadata_count: degraded_metadata_count(&outcome.streamed_payload)?,
490    })
491}
492
493pub fn extract_unencrypted_non_seekable_stream_to_dir_with_bootstrap_sidecar<R: Read>(
494    reader: R,
495    bootstrap_sidecar: &[u8],
496    output_dir: &Path,
497    options: NonSeekableReaderOptions,
498    extraction: SafeExtractionOptions,
499) -> Result<SequentialExtractReport, ExtractError> {
500    let staging = StagedExtraction::new(output_dir)?;
501    let observer = TarStreamFilesystemRestoreObserver::new(
502        staging.root(),
503        SafeExtractionOptions {
504            overwrite_existing: true,
505        },
506    );
507    let outcome = run_non_seekable_stream(
508        reader,
509        NonSeekableKeySource::MasterKey(None),
510        options,
511        observer,
512        Some(bootstrap_sidecar),
513    )?;
514    staging.commit(extraction)?;
515    Ok(SequentialExtractReport {
516        verification: outcome.verification,
517        extracted_member_count: outcome
518            .streamed_payload
519            .tar
520            .members
521            .len()
522            .try_into()
523            .map_err(|_| FormatError::InvalidArchive("extracted member count overflow"))?,
524        degraded_metadata_count: degraded_metadata_count(&outcome.streamed_payload)?,
525    })
526}
527
528pub fn list_non_seekable_stream<R: Read>(
529    reader: R,
530    master_key: &MasterKey,
531    options: NonSeekableReaderOptions,
532) -> Result<SequentialListReport, FormatError> {
533    let outcome = run_non_seekable_stream(
534        reader,
535        NonSeekableKeySource::MasterKey(Some(master_key)),
536        options,
537        NoopTarStreamObserver,
538        None,
539    )?;
540    let entries = streamed_list_entries(&outcome.opened, &outcome.streamed_payload)?;
541    Ok(SequentialListReport {
542        verification: outcome.verification,
543        entries,
544    })
545}
546
547pub fn list_non_seekable_stream_with_recipient_wrap_resolver<R, F>(
548    reader: R,
549    mut resolver: F,
550    options: NonSeekableReaderOptions,
551) -> Result<SequentialListReport, FormatError>
552where
553    R: Read,
554    F: FnMut(
555        RecipientWrapRecordContext<'_>,
556    ) -> Result<Vec<RecipientWrapCandidateMasterKey>, FormatError>,
557{
558    let outcome = run_non_seekable_stream(
559        reader,
560        NonSeekableKeySource::RecipientWrap(&mut resolver),
561        options,
562        NoopTarStreamObserver,
563        None,
564    )?;
565    let entries = streamed_list_entries(&outcome.opened, &outcome.streamed_payload)?;
566    Ok(SequentialListReport {
567        verification: outcome.verification,
568        entries,
569    })
570}
571
572pub fn list_unencrypted_non_seekable_stream<R: Read>(
573    reader: R,
574    options: NonSeekableReaderOptions,
575) -> Result<SequentialListReport, FormatError> {
576    let outcome = run_non_seekable_stream(
577        reader,
578        NonSeekableKeySource::MasterKey(None),
579        options,
580        NoopTarStreamObserver,
581        None,
582    )?;
583    let entries = streamed_list_entries(&outcome.opened, &outcome.streamed_payload)?;
584    Ok(SequentialListReport {
585        verification: outcome.verification,
586        entries,
587    })
588}
589
590pub fn list_non_seekable_stream_with_bootstrap_sidecar<R: Read>(
591    reader: R,
592    bootstrap_sidecar: &[u8],
593    master_key: &MasterKey,
594    options: NonSeekableReaderOptions,
595) -> Result<SequentialListReport, FormatError> {
596    let outcome = run_non_seekable_stream(
597        reader,
598        NonSeekableKeySource::MasterKey(Some(master_key)),
599        options,
600        NoopTarStreamObserver,
601        Some(bootstrap_sidecar),
602    )?;
603    let entries = streamed_list_entries(&outcome.opened, &outcome.streamed_payload)?;
604    Ok(SequentialListReport {
605        verification: outcome.verification,
606        entries,
607    })
608}
609
610pub fn list_non_seekable_stream_with_recipient_wrap_resolver_and_bootstrap_sidecar<R, F>(
611    reader: R,
612    bootstrap_sidecar: &[u8],
613    mut resolver: F,
614    options: NonSeekableReaderOptions,
615) -> Result<SequentialListReport, FormatError>
616where
617    R: Read,
618    F: FnMut(
619        RecipientWrapRecordContext<'_>,
620    ) -> Result<Vec<RecipientWrapCandidateMasterKey>, FormatError>,
621{
622    let outcome = run_non_seekable_stream(
623        reader,
624        NonSeekableKeySource::RecipientWrap(&mut resolver),
625        options,
626        NoopTarStreamObserver,
627        Some(bootstrap_sidecar),
628    )?;
629    let entries = streamed_list_entries(&outcome.opened, &outcome.streamed_payload)?;
630    Ok(SequentialListReport {
631        verification: outcome.verification,
632        entries,
633    })
634}
635
636pub fn list_unencrypted_non_seekable_stream_with_bootstrap_sidecar<R: Read>(
637    reader: R,
638    bootstrap_sidecar: &[u8],
639    options: NonSeekableReaderOptions,
640) -> Result<SequentialListReport, FormatError> {
641    let outcome = run_non_seekable_stream(
642        reader,
643        NonSeekableKeySource::MasterKey(None),
644        options,
645        NoopTarStreamObserver,
646        Some(bootstrap_sidecar),
647    )?;
648    let entries = streamed_list_entries(&outcome.opened, &outcome.streamed_payload)?;
649    Ok(SequentialListReport {
650        verification: outcome.verification,
651        entries,
652    })
653}
654
655struct SequentialStreamOutcome {
656    opened: OpenedArchive,
657    streamed_payload: StreamedPayloadSummary,
658    verification: SequentialVerifyReport,
659}
660
661type RecipientWrapResolver<'a> = dyn FnMut(
662        RecipientWrapRecordContext<'_>,
663    ) -> Result<Vec<RecipientWrapCandidateMasterKey>, FormatError>
664    + 'a;
665
666enum NonSeekableKeySource<'a> {
667    MasterKey(Option<&'a MasterKey>),
668    RecipientWrap(&'a mut RecipientWrapResolver<'a>),
669}
670
671fn run_non_seekable_stream<R, O>(
672    mut reader: R,
673    key_source: NonSeekableKeySource<'_>,
674    options: NonSeekableReaderOptions,
675    observer: O,
676    bootstrap_sidecar: Option<&[u8]>,
677) -> Result<SequentialStreamOutcome, FormatError>
678where
679    R: Read,
680    O: TarStreamObserver,
681{
682    validate_reader_options(options.reader)?;
683    let mut volume_header_bytes = [0u8; VOLUME_HEADER_LEN];
684    read_exact_stream(&mut reader, &mut volume_header_bytes, "VolumeHeader")?;
685    let volume_header = VolumeHeader::parse(&volume_header_bytes)?;
686    parse_volume_format_dispatch(&volume_header)?;
687
688    let crypto_len = usize::try_from(volume_header.crypto_header_length)
689        .map_err(|_| FormatError::InvalidArchive("CryptoHeader length overflow"))?;
690    let mut crypto_header_bytes = vec![0u8; crypto_len];
691    read_exact_stream(&mut reader, &mut crypto_header_bytes, "CryptoHeader")?;
692    let parsed_crypto =
693        CryptoHeader::parse(&crypto_header_bytes, volume_header.crypto_header_length)?;
694    let crypto_header = parsed_crypto.fixed.clone();
695    let mut stream_cursor = checked_u64_add(
696        VOLUME_HEADER_LEN as u64,
697        volume_header.crypto_header_length as u64,
698    )?;
699    let startup_key_wrap_table = startup_key_wrap_table(
700        &volume_header,
701        &parsed_crypto.kdf_params,
702        |start, length| {
703            if stream_cursor != start {
704                return Err(FormatError::InvalidArchive(
705                    "KeyWrapTableV1 does not start at stream cursor",
706                ));
707            }
708            let mut key_wrap_table_bytes = vec![0u8; length];
709            read_exact_stream(&mut reader, &mut key_wrap_table_bytes, "KeyWrapTableV1")?;
710            stream_cursor = checked_u64_add(stream_cursor, length as u64)?;
711            Ok(key_wrap_table_bytes)
712        },
713    )?;
714    let subkeys = match (
715        crypto_header.aead_algo.is_encrypted(),
716        &parsed_crypto.kdf_params,
717        key_source,
718    ) {
719        (false, _, NonSeekableKeySource::RecipientWrap(_)) => {
720            return Err(FormatError::KeyMaterialMismatch);
721        }
722        (false, _, NonSeekableKeySource::MasterKey(_)) => Subkeys::unencrypted_placeholder(),
723        (
724            true,
725            crate::crypto::KdfParams::RecipientWrap { .. },
726            NonSeekableKeySource::RecipientWrap(resolver),
727        ) => {
728            let table = startup_key_wrap_table
729                .as_ref()
730                .ok_or(FormatError::KeyMaterialMismatch)?;
731            recipient_wrap_subkeys_from_table(
732                &volume_header,
733                &parsed_crypto,
734                &table.table,
735                resolver,
736            )?
737        }
738        (
739            true,
740            crate::crypto::KdfParams::RecipientWrap { .. },
741            NonSeekableKeySource::MasterKey(_),
742        ) => {
743            return Err(FormatError::KeyMaterialMismatch);
744        }
745        (true, _, NonSeekableKeySource::MasterKey(master_key)) => Subkeys::derive(
746            master_key.ok_or(FormatError::KeyMaterialMismatch)?,
747            &volume_header.archive_uuid,
748            &volume_header.session_id,
749        )?,
750        (true, _, NonSeekableKeySource::RecipientWrap(_)) => {
751            return Err(FormatError::KeyMaterialMismatch);
752        }
753    };
754    verify_integrity_tag(
755        HmacDomain::CryptoHeader,
756        crypto_header.aead_algo,
757        volume_header.volume_format_rev,
758        Some(&subkeys.mac_key),
759        &volume_header.archive_uuid,
760        &volume_header.session_id,
761        parsed_crypto.hmac_covered_bytes,
762        &parsed_crypto.header_hmac,
763    )?;
764    parsed_crypto.validate_extension_semantics()?;
765    reject_unsupported_raw_stream_profile(&parsed_crypto.extensions)?;
766    validate_crypto_class_parity_exactness(&crypto_header)?;
767    let block_records_start = startup_key_wrap_table
768        .as_ref()
769        .map(|table| table.block_records_start)
770        .unwrap_or(stream_cursor);
771    let bootstrap = bootstrap_sidecar
772        .map(|sidecar| {
773            parse_non_seekable_bootstrap_material(sidecar, &volume_header, &crypto_header, &subkeys)
774        })
775        .transpose()?;
776    validate_sequential_verify_supported_volume(
777        &volume_header,
778        &crypto_header,
779        &parsed_crypto.extensions,
780        bootstrap.as_ref(),
781    )?;
782
783    let block_size = crypto_header.block_size as usize;
784    let record_len = block_size
785        .checked_add(BLOCK_RECORD_FRAMING_LEN)
786        .ok_or(FormatError::InvalidArchive("BlockRecord length overflow"))?;
787    let mut stream_offset = block_records_start;
788    let mut observed_block_count = 0u64;
789    let mut metadata_seen = false;
790    let mut pending = PendingLiveEnvelope::default();
791    let mut next_envelope_index = 0u64;
792    let mut retained_metadata_bytes = 0usize;
793    let mut metadata_blocks = BTreeMap::new();
794    let mut payload = StreamedPayloadCollector::with_observer(
795        &crypto_header,
796        options,
797        observer,
798        bootstrap
799            .as_ref()
800            .and_then(|material| material.payload_dictionary.clone()),
801    )?;
802
803    let terminal_tail = loop {
804        let mut magic = [0u8; 4];
805        read_exact_stream(&mut reader, &mut magic, "BlockRecord or terminal tail")?;
806        if magic != *b"TZBK" {
807            let tail_start = stream_offset;
808            stream_offset = checked_u64_add(stream_offset, magic.len() as u64)?;
809            let mut tail = TerminalTailBuffer::new(tail_start, options.max_terminal_tail_size);
810            tail.append(&magic)?;
811            let mut buf = [0u8; 64 * 1024];
812            loop {
813                let read = read_stream_chunk(&mut reader, &mut buf)?;
814                if read == 0 {
815                    break;
816                }
817                tail.append(&buf[..read])?;
818                stream_offset = checked_u64_add(stream_offset, read as u64)?;
819            }
820            break tail.finish(stream_offset);
821        }
822
823        let expected_block_index =
824            expected_stream_block_index(&volume_header, observed_block_count)?;
825        let mut raw = vec![0u8; record_len];
826        raw[..4].copy_from_slice(&magic);
827        read_exact_stream(&mut reader, &mut raw[4..], "BlockRecord")?;
828        observed_block_count = checked_u64_add(observed_block_count, 1)?;
829        stream_offset = checked_u64_add(stream_offset, record_len as u64)?;
830
831        match BlockRecord::parse(&raw, block_size) {
832            Ok(record) => {
833                if record.block_index != expected_block_index {
834                    return Err(FormatError::InvalidArchive(
835                        "BlockRecord index does not match stream position",
836                    ));
837                }
838                handle_live_record(
839                    record,
840                    &mut pending,
841                    LiveStreamContext {
842                        payload: &mut payload,
843                        subkeys: &subkeys,
844                        volume_header: &volume_header,
845                        crypto_header: &crypto_header,
846                        next_envelope_index: &mut next_envelope_index,
847                        metadata_seen: &mut metadata_seen,
848                        metadata_blocks: &mut metadata_blocks,
849                        retained_metadata_bytes: &mut retained_metadata_bytes,
850                        max_retained_metadata_bytes: options.max_retained_metadata_bytes,
851                    },
852                )?;
853            }
854            Err(err) if block_record_error_is_recoverable_erasure(&err) => {
855                handle_live_erasure(
856                    &mut pending,
857                    LiveStreamContext {
858                        payload: &mut payload,
859                        subkeys: &subkeys,
860                        volume_header: &volume_header,
861                        crypto_header: &crypto_header,
862                        next_envelope_index: &mut next_envelope_index,
863                        metadata_seen: &mut metadata_seen,
864                        metadata_blocks: &mut metadata_blocks,
865                        retained_metadata_bytes: &mut retained_metadata_bytes,
866                        max_retained_metadata_bytes: options.max_retained_metadata_bytes,
867                    },
868                    expected_block_index,
869                )?;
870            }
871            Err(err) => return Err(err),
872        }
873    };
874
875    if !pending.is_empty() {
876        finalize_live_envelope(
877            &mut pending,
878            &mut payload,
879            &subkeys,
880            &volume_header,
881            &crypto_header,
882            &mut next_envelope_index,
883        )?;
884    }
885
886    let terminal = parse_terminal_material_read_at(
887        &terminal_tail,
888        terminal_tail.stream_len,
889        terminal_tail.start_offset,
890        observed_block_count,
891        KeyHoldingTerminalContext {
892            subkeys: &subkeys,
893            volume_header: &volume_header,
894            crypto_header: &crypto_header,
895            crypto_header_bytes: &crypto_header_bytes,
896        },
897    )?;
898    if let Some(bootstrap) = &bootstrap {
899        if !manifest_bootstrap_fields_match(&terminal.manifest_footer, &bootstrap.manifest_footer) {
900            return Err(FormatError::InvalidArchive(
901                "bootstrap sidecar conflicts with terminal ManifestFooter",
902            ));
903        }
904    }
905    let observed_archive_bytes = observed_archive_size([terminal_tail.stream_len])?;
906    let streamed_payload = payload.finish()?;
907    if streamed_payload.tar.total_extraction_size
908        > total_extraction_size_cap(options.reader, observed_archive_bytes)
909    {
910        return Err(FormatError::ReaderUnsupported(
911            "total extraction size exceeds configured cap",
912        ));
913    }
914
915    let root_auth = root_auth_status(terminal.root_auth_footer.as_ref());
916    let opened = OpenedArchive::from_streamed_parts(StreamedArchiveOpenParts {
917        options: options.reader,
918        observed_archive_bytes,
919        subkeys,
920        blocks: metadata_blocks,
921        crypto_header_bytes,
922        volume_header,
923        crypto_header,
924        manifest_footer: terminal.manifest_footer,
925        volume_trailer: terminal.volume_trailer,
926        root_auth_footer: terminal.root_auth_footer,
927    })?;
928    opened.verify_streamed_payload_summary(&streamed_payload)?;
929
930    let verification = SequentialVerifyReport {
931        archive_uuid: opened.volume_header.archive_uuid,
932        session_id: opened.volume_header.session_id,
933        volume_format_rev: opened.volume_header.volume_format_rev,
934        volume_index: opened.volume_header.volume_index,
935        total_volumes: opened.manifest_footer.total_volumes,
936        file_count: opened.index_root.header.file_count,
937        payload_block_count: opened.index_root.header.payload_block_count,
938        tar_total_size: opened.index_root.header.tar_total_size,
939        content_sha256: opened.index_root.header.content_sha256,
940        root_auth,
941    };
942
943    Ok(SequentialStreamOutcome {
944        opened,
945        streamed_payload,
946        verification,
947    })
948}
949
950fn degraded_metadata_count(payload: &StreamedPayloadSummary) -> Result<u64, FormatError> {
951    payload.tar.members.iter().try_fold(0u64, |count, member| {
952        count
953            .checked_add(member.diagnostics.len() as u64)
954            .ok_or(FormatError::InvalidArchive(
955                "degraded metadata count overflow",
956            ))
957    })
958}
959
960fn streamed_list_entries(
961    opened: &OpenedArchive,
962    payload: &StreamedPayloadSummary,
963) -> Result<Vec<ArchiveEntry>, FormatError> {
964    let mut latest_by_path = BTreeMap::<Vec<u8>, &TarStreamMemberSummary>::new();
965    for member in &payload.tar.members {
966        let replace = latest_by_path
967            .get(&member.path)
968            .map(|existing| member.group_start > existing.group_start)
969            .unwrap_or(true);
970        if replace {
971            latest_by_path.insert(member.path.clone(), member);
972        }
973    }
974
975    opened
976        .list_index_entries()?
977        .into_iter()
978        .map(|entry| {
979            let member =
980                latest_by_path
981                    .get(entry.path.as_bytes())
982                    .ok_or(FormatError::InvalidArchive(
983                        "streamed tar member missing from final index",
984                    ))?;
985            Ok(ArchiveEntry {
986                path: entry.path,
987                file_data_size: entry.file_data_size,
988                kind: member.kind,
989                mode: member.mode,
990                mtime: member.mtime,
991                diagnostics: member.diagnostics.clone(),
992            })
993        })
994        .collect()
995}
996
997struct StagedExtraction {
998    tempdir: tempfile::TempDir,
999    root: PathBuf,
1000    output_dir: PathBuf,
1001}
1002
1003impl StagedExtraction {
1004    fn new(output_dir: &Path) -> Result<Self, ExtractError> {
1005        let parent = output_dir
1006            .parent()
1007            .filter(|path| !path.as_os_str().is_empty())
1008            .unwrap_or_else(|| Path::new("."));
1009        let tempdir = tempfile::Builder::new()
1010            .prefix(".tzap-nonseekable-")
1011            .tempdir_in(parent)
1012            .map_err(ExtractError::Output)?;
1013        let root = tempdir.path().join("root");
1014        fs::create_dir(&root).map_err(ExtractError::Output)?;
1015        Ok(Self {
1016            tempdir,
1017            root,
1018            output_dir: output_dir.to_path_buf(),
1019        })
1020    }
1021
1022    fn root(&self) -> &Path {
1023        &self.root
1024    }
1025
1026    fn commit(self, options: SafeExtractionOptions) -> Result<(), ExtractError> {
1027        match fs::symlink_metadata(&self.output_dir) {
1028            Ok(metadata) => {
1029                if metadata.file_type().is_symlink() || !metadata.file_type().is_dir() {
1030                    return Err(FormatError::UnsafeArchivePath.into());
1031                }
1032                preflight_staged_merge(&self.root, &self.output_dir, options)?;
1033                merge_staged_dir(&self.root, &self.output_dir, options)?;
1034            }
1035            Err(error) if error.kind() == ErrorKind::NotFound => {
1036                if let Some(parent) = self
1037                    .output_dir
1038                    .parent()
1039                    .filter(|path| !path.as_os_str().is_empty())
1040                {
1041                    fs::create_dir_all(parent).map_err(ExtractError::Output)?;
1042                }
1043                fs::rename(&self.root, &self.output_dir).map_err(ExtractError::Output)?;
1044            }
1045            Err(_) => {
1046                return Err(FormatError::FilesystemExtractionFailed(
1047                    "failed to inspect extraction directory",
1048                )
1049                .into());
1050            }
1051        }
1052        drop(self.tempdir);
1053        Ok(())
1054    }
1055}
1056
1057fn preflight_staged_merge(
1058    staged_root: &Path,
1059    output_root: &Path,
1060    options: SafeExtractionOptions,
1061) -> Result<(), FormatError> {
1062    for entry in read_dir_sorted(staged_root)? {
1063        let staged_path = entry.path();
1064        let relative = staged_path
1065            .strip_prefix(staged_root)
1066            .map_err(|_| FormatError::UnsafeArchivePath)?;
1067        preflight_staged_entry(&staged_path, output_root, relative, options)?;
1068    }
1069    Ok(())
1070}
1071
1072fn preflight_staged_entry(
1073    staged_path: &Path,
1074    output_root: &Path,
1075    relative: &Path,
1076    options: SafeExtractionOptions,
1077) -> Result<(), FormatError> {
1078    let final_path = output_root.join(relative);
1079    let staged_metadata = fs::symlink_metadata(staged_path).map_err(|_| {
1080        FormatError::FilesystemExtractionFailed("failed to inspect staged extraction output")
1081    })?;
1082    if let Some(parent) = relative
1083        .parent()
1084        .filter(|path| !path.as_os_str().is_empty())
1085    {
1086        preflight_relative_parent_chain(output_root, parent)?;
1087    }
1088    match fs::symlink_metadata(&final_path) {
1089        Ok(final_metadata) => {
1090            let final_type = final_metadata.file_type();
1091            if final_type.is_symlink() {
1092                return Err(FormatError::UnsafeArchivePath);
1093            }
1094            if staged_metadata.file_type().is_dir() {
1095                if !final_type.is_dir() {
1096                    return Err(FormatError::UnsafeOverwrite);
1097                }
1098            } else if final_type.is_dir() || !options.overwrite_existing {
1099                return Err(FormatError::UnsafeOverwrite);
1100            }
1101        }
1102        Err(error) if error.kind() == ErrorKind::NotFound => {}
1103        Err(_) => {
1104            return Err(FormatError::FilesystemExtractionFailed(
1105                "failed to inspect extraction destination",
1106            ));
1107        }
1108    }
1109    if staged_metadata.file_type().is_dir() {
1110        for entry in read_dir_sorted(staged_path)? {
1111            let child_relative = relative.join(entry.file_name());
1112            preflight_staged_entry(&entry.path(), output_root, &child_relative, options)?;
1113        }
1114    }
1115    Ok(())
1116}
1117
1118fn preflight_relative_parent_chain(root: &Path, parent: &Path) -> Result<(), FormatError> {
1119    let mut current = root.to_path_buf();
1120    for component in parent.components() {
1121        current.push(component.as_os_str());
1122        match fs::symlink_metadata(&current) {
1123            Ok(metadata) => {
1124                let file_type = metadata.file_type();
1125                if file_type.is_symlink() || !file_type.is_dir() {
1126                    return Err(FormatError::UnsafeArchivePath);
1127                }
1128            }
1129            Err(error) if error.kind() == ErrorKind::NotFound => {}
1130            Err(_) => {
1131                return Err(FormatError::FilesystemExtractionFailed(
1132                    "failed to inspect extraction destination",
1133                ));
1134            }
1135        }
1136    }
1137    Ok(())
1138}
1139
1140fn merge_staged_dir(
1141    staged_dir: &Path,
1142    final_dir: &Path,
1143    options: SafeExtractionOptions,
1144) -> Result<(), ExtractError> {
1145    fs::create_dir_all(final_dir).map_err(ExtractError::Output)?;
1146    for entry in read_dir_sorted(staged_dir)? {
1147        let staged_path = entry.path();
1148        let final_path = final_dir.join(entry.file_name());
1149        let metadata = fs::symlink_metadata(&staged_path).map_err(|_| {
1150            FormatError::FilesystemExtractionFailed("failed to inspect staged extraction output")
1151        })?;
1152        if metadata.file_type().is_dir() {
1153            merge_staged_dir(&staged_path, &final_path, options)?;
1154            continue;
1155        }
1156        if options.overwrite_existing && fs::symlink_metadata(&final_path).is_ok() {
1157            fs::remove_file(&final_path).map_err(ExtractError::Output)?;
1158        }
1159        fs::rename(&staged_path, &final_path).map_err(ExtractError::Output)?;
1160    }
1161    Ok(())
1162}
1163
1164fn read_dir_sorted(path: &Path) -> Result<Vec<fs::DirEntry>, FormatError> {
1165    let mut entries = fs::read_dir(path)
1166        .map_err(|_| FormatError::FilesystemExtractionFailed("failed to read directory"))?
1167        .collect::<Result<Vec<_>, _>>()
1168        .map_err(|_| FormatError::FilesystemExtractionFailed("failed to read directory"))?;
1169    entries.sort_by_key(|entry| entry.file_name());
1170    Ok(entries)
1171}
1172
1173fn validate_sequential_verify_supported_volume(
1174    volume_header: &VolumeHeader,
1175    crypto_header: &CryptoHeaderFixed,
1176    extensions: &[ExtensionTlv<'_>],
1177    bootstrap: Option<&NonSeekableBootstrapMaterial>,
1178) -> Result<(), FormatError> {
1179    reject_unsupported_raw_stream_profile(extensions)?;
1180    if volume_header.stripe_width != 1 || volume_header.volume_index != 0 {
1181        return Err(FormatError::ReaderUnsupported(
1182            "sequential reader supports only single-volume archive input",
1183        ));
1184    }
1185    if crypto_header.stripe_width != volume_header.stripe_width {
1186        return Err(FormatError::InvalidArchive(
1187            "VolumeHeader and CryptoHeader stripe_width differ",
1188        ));
1189    }
1190    if crypto_header.has_dictionary != 0
1191        && bootstrap
1192            .and_then(|material| material.payload_dictionary.as_ref())
1193            .is_none()
1194    {
1195        return Err(FormatError::ReaderUnsupported(
1196            "dictionary bootstrap required for non-seekable sequential verification",
1197        ));
1198    }
1199    Ok(())
1200}
1201
1202struct LiveStreamContext<'a, O: TarStreamObserver> {
1203    payload: &'a mut StreamedPayloadCollector<O>,
1204    subkeys: &'a Subkeys,
1205    volume_header: &'a VolumeHeader,
1206    crypto_header: &'a CryptoHeaderFixed,
1207    next_envelope_index: &'a mut u64,
1208    metadata_seen: &'a mut bool,
1209    metadata_blocks: &'a mut BTreeMap<u64, BlockRecord>,
1210    retained_metadata_bytes: &'a mut usize,
1211    max_retained_metadata_bytes: usize,
1212}
1213
1214fn handle_live_record<O: TarStreamObserver>(
1215    record: BlockRecord,
1216    pending: &mut PendingLiveEnvelope,
1217    context: LiveStreamContext<'_, O>,
1218) -> Result<(), FormatError> {
1219    match record.kind {
1220        BlockKind::PayloadData => {
1221            if *context.metadata_seen {
1222                return Err(FormatError::InvalidArchive(
1223                    "payload BlockRecord appears after metadata",
1224                ));
1225            }
1226            if pending.awaiting_tentative_parity {
1227                return Err(FormatError::InvalidArchive(
1228                    "sequential payload envelope boundary is ambiguous after CRC erasure",
1229                ));
1230            }
1231            if pending.saw_last_data {
1232                finalize_live_envelope(
1233                    pending,
1234                    &mut *context.payload,
1235                    context.subkeys,
1236                    context.volume_header,
1237                    context.crypto_header,
1238                    &mut *context.next_envelope_index,
1239                )?;
1240            }
1241            pending.note_block(record.block_index);
1242            let is_last_data = record.is_last_data();
1243            pending.data_shards.push(Some(record.payload));
1244            if is_last_data {
1245                pending.saw_last_data = true;
1246            }
1247            if pending.data_shards.len() > context.crypto_header.fec_data_shards as usize {
1248                return Err(FormatError::InvalidArchive(
1249                    "sequential payload envelope exceeds data-shard cap",
1250                ));
1251            }
1252        }
1253        BlockKind::PayloadParity => {
1254            if *context.metadata_seen {
1255                return Err(FormatError::InvalidArchive(
1256                    "payload parity BlockRecord appears after metadata",
1257                ));
1258            }
1259            if pending.awaiting_tentative_parity {
1260                pending.awaiting_tentative_parity = false;
1261                pending.saw_last_data = true;
1262            } else if pending.data_shards.is_empty() || !pending.saw_last_data {
1263                return Err(FormatError::InvalidArchive(
1264                    "payload parity appears before envelope data is complete",
1265                ));
1266            }
1267            pending.note_block(record.block_index);
1268            pending.parity_shards.push(Some(record.payload));
1269            if pending.parity_shards.len() > context.crypto_header.fec_parity_shards as usize {
1270                return Err(FormatError::InvalidArchive(
1271                    "sequential payload envelope exceeds parity-shard cap",
1272                ));
1273            }
1274        }
1275        _ => {
1276            if !pending.is_empty() {
1277                finalize_live_envelope(
1278                    pending,
1279                    &mut *context.payload,
1280                    context.subkeys,
1281                    context.volume_header,
1282                    context.crypto_header,
1283                    &mut *context.next_envelope_index,
1284                )?;
1285            }
1286            *context.metadata_seen = true;
1287            retain_metadata_record(
1288                &mut *context.metadata_blocks,
1289                record,
1290                &mut *context.retained_metadata_bytes,
1291                context.max_retained_metadata_bytes,
1292            )?;
1293        }
1294    }
1295    Ok(())
1296}
1297
1298fn retain_metadata_record(
1299    metadata_blocks: &mut BTreeMap<u64, BlockRecord>,
1300    record: BlockRecord,
1301    retained_metadata_bytes: &mut usize,
1302    max_retained_metadata_bytes: usize,
1303) -> Result<(), FormatError> {
1304    let retained = record
1305        .payload
1306        .len()
1307        .checked_add(BLOCK_RECORD_FRAMING_LEN)
1308        .ok_or(FormatError::InvalidArchive("metadata retention overflow"))?;
1309    *retained_metadata_bytes = retained_metadata_bytes
1310        .checked_add(retained)
1311        .ok_or(FormatError::InvalidArchive("metadata retention overflow"))?;
1312    if *retained_metadata_bytes > max_retained_metadata_bytes {
1313        return Err(FormatError::ReaderUnsupported(
1314            "retained metadata exceeds configured streaming cap",
1315        ));
1316    }
1317    if metadata_blocks.insert(record.block_index, record).is_some() {
1318        return Err(FormatError::InvalidArchive("duplicate BlockRecord index"));
1319    }
1320    Ok(())
1321}
1322
1323fn handle_live_erasure<O: TarStreamObserver>(
1324    pending: &mut PendingLiveEnvelope,
1325    context: LiveStreamContext<'_, O>,
1326    expected_block_index: u64,
1327) -> Result<(), FormatError> {
1328    if *context.metadata_seen {
1329        return Ok(());
1330    }
1331    if pending.saw_last_data
1332        && pending.parity_shards.len()
1333            >= required_object_parity(pending.data_shards.len() as u64, context.crypto_header)?
1334                as usize
1335    {
1336        finalize_live_envelope(
1337            pending,
1338            &mut *context.payload,
1339            context.subkeys,
1340            context.volume_header,
1341            context.crypto_header,
1342            &mut *context.next_envelope_index,
1343        )?;
1344        *context.metadata_seen = true;
1345        return Ok(());
1346    }
1347    if pending.saw_last_data {
1348        return Err(FormatError::BadCrc {
1349            structure: "BlockRecord",
1350        });
1351    }
1352    if !sequential_payload_parity_is_guaranteed(context.crypto_header) {
1353        return Err(FormatError::BadCrc {
1354            structure: "BlockRecord",
1355        });
1356    }
1357    pending.note_block(expected_block_index);
1358    pending.data_shards.push(None);
1359    pending.awaiting_tentative_parity = true;
1360    if pending.data_shards.len() > context.crypto_header.fec_data_shards as usize {
1361        return Err(FormatError::InvalidArchive(
1362            "sequential payload envelope exceeds data-shard cap",
1363        ));
1364    }
1365    Ok(())
1366}
1367
1368fn sequential_payload_parity_is_guaranteed(crypto_header: &CryptoHeaderFixed) -> bool {
1369    crypto_header.fec_parity_shards > 0
1370        && (crypto_header.volume_loss_tolerance > 0 || crypto_header.bit_rot_buffer_pct > 0)
1371}
1372
1373fn finalize_live_envelope<O: TarStreamObserver>(
1374    pending: &mut PendingLiveEnvelope,
1375    payload: &mut StreamedPayloadCollector<O>,
1376    subkeys: &Subkeys,
1377    volume_header: &VolumeHeader,
1378    crypto_header: &CryptoHeaderFixed,
1379    next_envelope_index: &mut u64,
1380) -> Result<(), FormatError> {
1381    if !pending.saw_last_data {
1382        return Err(FormatError::InvalidArchive(
1383            "sequential payload envelope is missing last-data flag",
1384        ));
1385    }
1386    if pending.data_shards.len() > crypto_header.fec_data_shards as usize {
1387        return Err(FormatError::InvalidArchive(
1388            "sequential payload envelope exceeds data-shard cap",
1389        ));
1390    }
1391    if pending.parity_shards.len() > crypto_header.fec_parity_shards as usize {
1392        return Err(FormatError::InvalidArchive(
1393            "sequential payload envelope exceeds parity-shard cap",
1394        ));
1395    }
1396    let required_parity = required_object_parity(pending.data_shards.len() as u64, crypto_header)?;
1397    if pending.parity_shards.len() < required_parity as usize {
1398        return Err(FormatError::InvalidArchive(
1399            "sequential payload envelope has insufficient parity for recovery settings",
1400        ));
1401    }
1402    let first_block_index = pending
1403        .first_block_index
1404        .ok_or(FormatError::InvalidArchive(
1405            "sequential payload envelope is missing first block",
1406        ))?;
1407
1408    let repaired = repair_data_gf16(
1409        &pending.data_shards,
1410        &pending.parity_shards,
1411        crypto_header.block_size as usize,
1412    )?;
1413    let mut encrypted = Vec::with_capacity(repaired.len() * crypto_header.block_size as usize);
1414    for shard in repaired {
1415        encrypted.extend_from_slice(&shard);
1416    }
1417    let plaintext = decrypt_padded_aead_object(
1418        AeadObjectContext {
1419            algo: crypto_header.aead_algo,
1420            key: &subkeys.enc_key,
1421            nonce_seed: &subkeys.nonce_seed,
1422            domain: b"envelope",
1423            archive_uuid: &volume_header.archive_uuid,
1424            session_id: &volume_header.session_id,
1425            counter: *next_envelope_index,
1426        },
1427        &encrypted,
1428    )?;
1429    payload.decode_envelope(
1430        *next_envelope_index,
1431        first_block_index,
1432        pending.data_shards.len(),
1433        pending.parity_shards.len(),
1434        crypto_header.block_size as usize,
1435        &plaintext,
1436    )?;
1437    *next_envelope_index = checked_u64_add(*next_envelope_index, 1)?;
1438    *pending = PendingLiveEnvelope::default();
1439    Ok(())
1440}
1441
1442#[derive(Debug, Default)]
1443struct PendingLiveEnvelope {
1444    first_block_index: Option<u64>,
1445    data_shards: Vec<Option<Vec<u8>>>,
1446    parity_shards: Vec<Option<Vec<u8>>>,
1447    saw_last_data: bool,
1448    awaiting_tentative_parity: bool,
1449}
1450
1451impl PendingLiveEnvelope {
1452    fn is_empty(&self) -> bool {
1453        self.data_shards.is_empty() && self.parity_shards.is_empty()
1454    }
1455
1456    fn note_block(&mut self, block_index: u64) {
1457        if self.first_block_index.is_none() {
1458            self.first_block_index = Some(block_index);
1459        }
1460    }
1461}
1462
1463struct StreamedPayloadCollector<O = NoopTarStreamObserver> {
1464    tar: TarStreamSummaryValidator<O>,
1465    hasher: Sha256,
1466    max_tar_stream_size: usize,
1467    payload_dictionary: Option<Vec<u8>>,
1468    envelopes: Vec<StreamedEnvelopeSummary>,
1469    frames: Vec<StreamedFrameSummary>,
1470}
1471
1472impl<O: TarStreamObserver> StreamedPayloadCollector<O> {
1473    fn with_observer(
1474        crypto_header: &CryptoHeaderFixed,
1475        options: NonSeekableReaderOptions,
1476        observer: O,
1477        payload_dictionary: Option<Vec<u8>>,
1478    ) -> Result<Self, FormatError> {
1479        Ok(Self {
1480            tar: TarStreamSummaryValidator::with_observer(
1481                crypto_header.max_path_length,
1482                options.reader.max_total_extraction_size,
1483                options.max_incomplete_tar_group_bytes,
1484                options.max_streamed_member_count,
1485                observer,
1486            ),
1487            hasher: Sha256::new(),
1488            max_tar_stream_size: options.reader.max_verify_tar_size,
1489            payload_dictionary,
1490            envelopes: Vec::new(),
1491            frames: Vec::new(),
1492        })
1493    }
1494
1495    fn decode_envelope(
1496        &mut self,
1497        envelope_index: u64,
1498        first_block_index: u64,
1499        data_block_count: usize,
1500        parity_block_count: usize,
1501        block_size: usize,
1502        plaintext: &[u8],
1503    ) -> Result<(), FormatError> {
1504        if plaintext.is_empty() {
1505            return Err(FormatError::InvalidArchive(
1506                "payload envelope plaintext has no frames",
1507            ));
1508        }
1509        let first_frame_index = u64::try_from(self.frames.len())
1510            .map_err(|_| FormatError::InvalidArchive("FrameEntry count overflow"))?;
1511        let mut cursor = 0usize;
1512        while cursor < plaintext.len() {
1513            let frame_len = zstd_safe::find_frame_compressed_size(&plaintext[cursor..])
1514                .map_err(|_| FormatError::InvalidZstdFrame)?;
1515            if frame_len == 0 {
1516                return Err(FormatError::InvalidZstdFrame);
1517            }
1518            let end = checked_usize_add(cursor, frame_len)?;
1519            validate_exact_zstd_frame(&plaintext[cursor..end])?;
1520            self.decode_frame(
1521                envelope_index,
1522                u32_len(cursor, "FrameEntry.offset_in_envelope")?,
1523                &plaintext[cursor..end],
1524            )?;
1525            cursor = end;
1526        }
1527        let frame_count = u32_len(
1528            self.frames.len() - first_frame_index as usize,
1529            "EnvelopeEntry.frame_count",
1530        )?;
1531        if frame_count == 0 {
1532            return Err(FormatError::InvalidArchive(
1533                "payload envelope plaintext has no frames",
1534            ));
1535        }
1536        let encrypted_size =
1537            data_block_count
1538                .checked_mul(block_size)
1539                .ok_or(FormatError::InvalidArchive(
1540                    "EnvelopeEntry encrypted size overflow",
1541                ))?;
1542        self.envelopes.push(StreamedEnvelopeSummary {
1543            envelope_index,
1544            first_block_index,
1545            data_block_count: u32_len(data_block_count, "EnvelopeEntry.data_block_count")?,
1546            parity_block_count: u32_len(parity_block_count, "EnvelopeEntry.parity_block_count")?,
1547            encrypted_size: u32_len(encrypted_size, "EnvelopeEntry.encrypted_size")?,
1548            plaintext_size: u32_len(plaintext.len(), "EnvelopeEntry.plaintext_size")?,
1549            first_frame_index,
1550            frame_count,
1551        });
1552        Ok(())
1553    }
1554
1555    fn decode_frame(
1556        &mut self,
1557        envelope_index: u64,
1558        offset_in_envelope: u32,
1559        compressed: &[u8],
1560    ) -> Result<(), FormatError> {
1561        let frame_index = u64::try_from(self.frames.len())
1562            .map_err(|_| FormatError::InvalidArchive("FrameEntry count overflow"))?;
1563        let tar_stream_offset = self.tar.tar_total_size();
1564        let decompressed_size = if let Some(dictionary) = &self.payload_dictionary {
1565            let mut decoder = zstd::stream::Decoder::with_dictionary(compressed, dictionary)
1566                .map_err(|_| FormatError::ZstdDecompressionFailure)?;
1567            self.decode_zstd_frame_body(&mut decoder)?
1568        } else {
1569            let mut decoder = zstd::stream::Decoder::new(compressed)
1570                .map_err(|_| FormatError::ZstdDecompressionFailure)?;
1571            self.decode_zstd_frame_body(&mut decoder)?
1572        };
1573        if decompressed_size == 0 {
1574            return Err(FormatError::InvalidArchive(
1575                "zstd payload frame decompressed to zero bytes",
1576            ));
1577        }
1578        self.frames.push(StreamedFrameSummary {
1579            frame_index,
1580            envelope_index,
1581            offset_in_envelope,
1582            compressed_size: u32_len(compressed.len(), "FrameEntry.compressed_size")?,
1583            decompressed_size: u32_len(
1584                usize::try_from(decompressed_size)
1585                    .map_err(|_| FormatError::InvalidArchive("FrameEntry size overflow"))?,
1586                "FrameEntry.decompressed_size",
1587            )?,
1588            tar_stream_offset,
1589        });
1590        Ok(())
1591    }
1592
1593    fn decode_zstd_frame_body<D: Read>(&mut self, decoder: &mut D) -> Result<u64, FormatError> {
1594        let mut decompressed_size = 0u64;
1595        let mut buf = [0u8; 64 * 1024];
1596        loop {
1597            let read = decoder
1598                .read(&mut buf)
1599                .map_err(|_| FormatError::ZstdDecompressionFailure)?;
1600            if read == 0 {
1601                break;
1602            }
1603            let next_tar_size = self.tar.tar_total_size().checked_add(read as u64).ok_or(
1604                FormatError::ReaderUnsupported(
1605                    "sequential tar stream exceeds configured verification cap",
1606                ),
1607            )?;
1608            if next_tar_size > self.max_tar_stream_size as u64 {
1609                return Err(FormatError::ReaderUnsupported(
1610                    "sequential tar stream exceeds configured verification cap",
1611                ));
1612            }
1613            self.hasher.update(&buf[..read]);
1614            self.tar.observe(&buf[..read])?;
1615            decompressed_size = checked_u64_add(decompressed_size, read as u64)?;
1616        }
1617        Ok(decompressed_size)
1618    }
1619
1620    fn finish(self) -> Result<StreamedPayloadSummary, FormatError> {
1621        let content_sha256 = self.hasher.finalize();
1622        let mut digest = [0u8; 32];
1623        digest.copy_from_slice(&content_sha256);
1624        Ok(StreamedPayloadSummary {
1625            tar: self.tar.finish()?,
1626            content_sha256: digest,
1627            envelopes: self.envelopes,
1628            frames: self.frames,
1629        })
1630    }
1631}
1632
1633struct TerminalTailBuffer {
1634    start_offset: u64,
1635    cap: usize,
1636    bytes: Vec<u8>,
1637}
1638
1639impl TerminalTailBuffer {
1640    fn new(start_offset: u64, cap: usize) -> Self {
1641        Self {
1642            start_offset,
1643            cap,
1644            bytes: Vec::new(),
1645        }
1646    }
1647
1648    fn append(&mut self, bytes: &[u8]) -> Result<(), FormatError> {
1649        let next_len = self
1650            .bytes
1651            .len()
1652            .checked_add(bytes.len())
1653            .ok_or(FormatError::InvalidArchive("terminal tail size overflow"))?;
1654        if next_len > self.cap {
1655            return Err(FormatError::ReaderUnsupported(
1656                "terminal tail exceeds configured cap",
1657            ));
1658        }
1659        self.bytes.extend_from_slice(bytes);
1660        Ok(())
1661    }
1662
1663    fn finish(self, stream_len: u64) -> TerminalTailReadAt {
1664        TerminalTailReadAt {
1665            start_offset: self.start_offset,
1666            stream_len,
1667            bytes: self.bytes,
1668        }
1669    }
1670}
1671
1672struct TerminalTailReadAt {
1673    start_offset: u64,
1674    stream_len: u64,
1675    bytes: Vec<u8>,
1676}
1677
1678impl ArchiveReadAt for TerminalTailReadAt {
1679    fn len(&self) -> Result<u64, FormatError> {
1680        Ok(self.stream_len)
1681    }
1682
1683    fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
1684        let end = offset
1685            .checked_add(buf.len() as u64)
1686            .ok_or(FormatError::InvalidArchive("terminal tail range overflow"))?;
1687        let tail_end = self
1688            .start_offset
1689            .checked_add(self.bytes.len() as u64)
1690            .ok_or(FormatError::InvalidArchive("terminal tail range overflow"))?;
1691        if offset < self.start_offset || end > tail_end {
1692            return Err(FormatError::InvalidLength {
1693                structure: "terminal tail",
1694                expected: usize::try_from(end.saturating_sub(self.start_offset))
1695                    .unwrap_or(usize::MAX),
1696                actual: self.bytes.len(),
1697            });
1698        }
1699        let start = usize::try_from(offset - self.start_offset)
1700            .map_err(|_| FormatError::InvalidArchive("terminal tail range overflow"))?;
1701        let end = start
1702            .checked_add(buf.len())
1703            .ok_or(FormatError::InvalidArchive("terminal tail range overflow"))?;
1704        buf.copy_from_slice(&self.bytes[start..end]);
1705        Ok(())
1706    }
1707}
1708
1709fn root_auth_status(footer: Option<&RootAuthFooterV1>) -> SequentialRootAuthStatus {
1710    if footer.is_some() {
1711        SequentialRootAuthStatus::WireValidOnly
1712    } else {
1713        SequentialRootAuthStatus::Absent
1714    }
1715}
1716
1717fn read_exact_stream<R: Read>(
1718    reader: &mut R,
1719    mut buf: &mut [u8],
1720    structure: &'static str,
1721) -> Result<(), FormatError> {
1722    let expected = buf.len();
1723    let mut actual = 0usize;
1724    while !buf.is_empty() {
1725        match reader.read(buf) {
1726            Ok(0) => {
1727                return Err(FormatError::InvalidLength {
1728                    structure,
1729                    expected,
1730                    actual,
1731                })
1732            }
1733            Ok(read) => {
1734                actual = checked_usize_add(actual, read)?;
1735                let (_, rest) = buf.split_at_mut(read);
1736                buf = rest;
1737            }
1738            Err(err) if err.kind() == ErrorKind::Interrupted => {}
1739            Err(_) => return Err(FormatError::InvalidArchive("archive read failed")),
1740        }
1741    }
1742    Ok(())
1743}
1744
1745fn read_stream_chunk<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<usize, FormatError> {
1746    loop {
1747        match reader.read(buf) {
1748            Ok(read) => return Ok(read),
1749            Err(err) if err.kind() == ErrorKind::Interrupted => {}
1750            Err(_) => return Err(FormatError::InvalidArchive("archive read failed")),
1751        }
1752    }
1753}
1754
1755fn checked_u64_add(lhs: u64, rhs: u64) -> Result<u64, FormatError> {
1756    lhs.checked_add(rhs)
1757        .ok_or(FormatError::InvalidArchive("stream arithmetic overflow"))
1758}
1759
1760fn checked_usize_add(lhs: usize, rhs: usize) -> Result<usize, FormatError> {
1761    lhs.checked_add(rhs)
1762        .ok_or(FormatError::InvalidArchive("stream arithmetic overflow"))
1763}
1764
1765fn u32_len(value: usize, structure: &'static str) -> Result<u32, FormatError> {
1766    u32::try_from(value).map_err(|_| FormatError::InvalidArchive(structure))
1767}