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