1use std::collections::{HashMap, VecDeque};
2use std::fs::File;
3use std::io::{self, Read, Seek, SeekFrom};
4use std::num::NonZeroUsize;
5use std::path::{Path, PathBuf};
6use std::sync::{
7 Arc, Condvar, Mutex,
8 atomic::{AtomicBool, Ordering},
9};
10use std::time::Instant;
11
12use lru::LruCache;
13pub(crate) mod recovery;
14use md5::Digest as _;
15
16use crate::codepage::decode_header_bytes;
17use crate::decode::{
18 ChunkEncoding, decode_chunk_with_padding, raw_chunk_size_cap, validate_encoded_size,
19 zlib_compressed_chunk_size_cap,
20};
21use crate::encryption::EncryptionContext;
22use crate::format::{ewf1, ewf2};
23use crate::index::{LazyChunkIndex, TableRange, TableRangeKind};
24use crate::metadata::{
25 Ewf2Geometry, detect_ewf1_header_profile, detect_ewf1_header2_profile, parse_error2_data,
26 parse_ewf2_case_data, parse_ewf2_device_info, parse_ewf2_device_info_values,
27 parse_ewf2_error_table_data, parse_header_data, parse_header2_data, parse_session_data,
28 parse_xhash_data, parse_xheader_data,
29};
30use crate::reader_cache::{TABLE_PAGE_SIZE, TablePageCache, TablePageKey};
31use crate::reader_statistics::{ReaderCacheInfo, ReaderStatistics, ReaderStatisticsCollector};
32use crate::segment::discover_segments;
33use crate::signature::{check_segment_files_corruption, check_segment_files_encryption};
34use crate::single_files::{parse_ewf1_single_files_data, parse_ewf2_single_files_data};
35use crate::types::{
36 AcquisitionError, ChunkCacheCapacity, CompressionLevel, CompressionMethod, CompressionValues,
37 DataChunk, DataChunkEncoding, EncodedDataChunk, EwfMetadata, Format, FormatProfile,
38 HeaderCodepage, HeaderDateFormat, ImageInfo, MediaFlags, MediaInfo, MediaType, MemoryExtent,
39 OpenOptions, OpenStrictness, SectorRange, SegmentFileVersion, SingleFileEntry,
40 SingleFilePermission, SingleFileSource, SingleFileSubject, SingleFilesAuxTables,
41 SingleFilesInfo, StoredHashes,
42};
43use crate::{
44 EncryptionInfo, EwfError, EwfPassword, Result, SectionInfo, SectionKind, SegmentSource,
45};
46
47const MAX_DECOMPRESSED_METADATA: u64 = 16 * 1024 * 1024;
48const MAX_CHUNK_SIZE: u64 = 128 * 1024 * 1024;
49const EWF1_HASH_SECTION_SIZE: u64 = 36;
50const EWF1_DIGEST_SECTION_SIZE: u64 = 80;
51const EWF1_LTREE_HEADER_SIZE: usize = 48;
52const EWF1_LOGICAL_SPARSE_FLAG: u32 = 0x0400_0000;
53const EWF2_HASH_SECTION_SIZE: u64 = 32;
54const EWF2_TABLE_HEADER_V2_SIZE: u64 = 32;
55const EWF2_TABLE_FOOTER_SIZE: u64 = 16;
56const TABLE_CHECKSUM_BUFFER_SIZE: usize = 64 * 1024;
57
58pub trait SegmentReader: Read + Seek + Send {
64 fn segment_len(&mut self) -> io::Result<u64> {
66 let position = self.stream_position()?;
67 let len = self.seek(SeekFrom::End(0))?;
68 self.seek(SeekFrom::Start(position))?;
69 Ok(len)
70 }
71}
72
73impl<T> SegmentReader for T where T: Read + Seek + Send {}
74
75type SuppliedSegmentReader = Box<dyn SegmentReader>;
76
77#[derive(Debug, Clone)]
78pub struct Image {
80 inner: Arc<ImageInner>,
81}
82
83#[derive(Debug)]
84struct ImageInner {
85 info: ImageInfo,
86 sections: Vec<SectionInfo>,
87 open_strictness: OpenStrictness,
88 encryption_info: Option<EncryptionInfo>,
89 encryption_contexts: Vec<Option<EncryptionContext>>,
90 segments: Mutex<SegmentFilePool>,
91 segment_handle_available: Condvar,
92 positioned_sources: Option<Vec<SegmentSource>>,
93 index: LazyChunkIndex,
94 chunk_cache: Mutex<LruCache<u64, Arc<Vec<u8>>>>,
95 chunk_in_flight: Mutex<HashMap<u64, Arc<ChunkFlight>>>,
96 chunk_cache_capacity_bytes: u64,
97 table_page_cache: Mutex<TablePageCache>,
98 statistics: Arc<ReaderStatisticsCollector>,
99 checksum_errors: Mutex<Vec<SectorRange>>,
100 read_zero_chunk_on_error: AtomicBool,
101 abort_signaled: AtomicBool,
102}
103
104struct SegmentFilePool {
105 files: Vec<Option<SegmentFileHandle>>,
106 lengths: Vec<Option<u64>>,
107 ever_opened: Vec<bool>,
108 open_order: VecDeque<usize>,
109 maximum_open_handles: Option<usize>,
110 mode: SegmentFilePoolMode,
111 statistics: Arc<ReaderStatisticsCollector>,
112}
113
114enum SegmentFileHandle {
115 Path(PathSegmentReader),
116 Supplied(SuppliedSegmentReader),
117}
118
119struct PathSegmentReader {
120 file: Arc<File>,
121 position: u64,
122}
123
124struct PositionedSegmentLease<'a> {
125 file: Option<Arc<File>>,
126 segments: &'a Mutex<SegmentFilePool>,
127 available: &'a Condvar,
128}
129
130#[derive(Debug)]
131struct ChunkFlight {
132 complete: Mutex<bool>,
133 ready: Condvar,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137enum SegmentFilePoolMode {
138 ReopenFromPath,
139 SuppliedReaders,
140}
141
142impl std::fmt::Debug for SegmentFilePool {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.debug_struct("SegmentFilePool")
145 .field("segment_count", &self.files.len())
146 .field(
147 "known_length_count",
148 &self
149 .lengths
150 .iter()
151 .filter(|length| length.is_some())
152 .count(),
153 )
154 .field(
155 "ever_opened_count",
156 &self.ever_opened.iter().filter(|opened| **opened).count(),
157 )
158 .field("open_count", &self.open_count())
159 .field("open_order", &self.open_order)
160 .field("maximum_open_handles", &self.maximum_open_handles)
161 .field("mode", &self.mode)
162 .field("statistics_enabled", &self.statistics.enabled())
163 .finish()
164 }
165}
166
167impl SegmentFileHandle {
168 fn as_reader_mut(&mut self) -> &mut dyn SegmentReader {
169 match self {
170 Self::Path(reader) => reader,
171 Self::Supplied(reader) => reader.as_mut(),
172 }
173 }
174
175 fn path_file(&self) -> Option<&Arc<File>> {
176 match self {
177 Self::Path(reader) => Some(&reader.file),
178 Self::Supplied(_) => None,
179 }
180 }
181
182 fn idle_path_handle(&self) -> bool {
183 self.path_file()
184 .is_some_and(|file| Arc::strong_count(file) == 1)
185 }
186}
187
188impl Read for PathSegmentReader {
189 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
190 let count = read_file_at(&self.file, buffer, self.position)?;
191 self.position = self.position.saturating_add(count as u64);
192 Ok(count)
193 }
194}
195
196impl Seek for PathSegmentReader {
197 fn seek(&mut self, seek: SeekFrom) -> io::Result<u64> {
198 let position = match seek {
199 SeekFrom::Start(offset) => Some(offset),
200 SeekFrom::Current(offset) => self.position.checked_add_signed(offset),
201 SeekFrom::End(offset) => self.file.metadata()?.len().checked_add_signed(offset),
202 }
203 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "segment seek out of range"))?;
204 self.position = position;
205 Ok(position)
206 }
207}
208
209impl Drop for PositionedSegmentLease<'_> {
210 fn drop(&mut self) {
211 self.file.take();
215 let _segments = self
218 .segments
219 .lock()
220 .unwrap_or_else(std::sync::PoisonError::into_inner);
221 self.available.notify_one();
222 }
223}
224
225impl PositionedSegmentLease<'_> {
226 fn file(&self) -> &File {
227 self.file
228 .as_deref()
229 .expect("positioned segment lease retains its file until drop")
230 }
231}
232
233#[derive(Debug, Clone, Copy)]
234struct Chunk {
235 segment_index: usize,
236 offset: u64,
237 encoded_size: u64,
238 logical_size: usize,
239 encoding: ChunkEncoding,
240 validate_checksum: bool,
241}
242
243#[derive(Clone, Copy)]
244struct Ewf1DecodedEntry {
245 compressed: bool,
246 offset: u64,
247}
248
249#[derive(Debug, Clone)]
250pub struct ImageCursor {
252 image: Image,
253 position: u64,
254}
255
256#[derive(Debug, Clone)]
257pub struct SingleFileCursor {
259 image: Image,
260 entry: SingleFileEntry,
261 position: u64,
262}
263
264impl Image {
265 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
274 Self::open_with_options(path, OpenOptions::default())
275 }
276
277 pub fn open_with_options(path: impl AsRef<Path>, options: OpenOptions) -> Result<Self> {
284 let paths = discover_segments(path.as_ref())?;
285 Self::open_segment_paths(paths, options, None)
286 }
287
288 pub fn open_with_password(path: impl AsRef<Path>, password: &EwfPassword) -> Result<Self> {
298 Self::open_with_options_and_password(path, OpenOptions::default(), password)
299 }
300
301 pub fn open_with_options_and_password(
312 path: impl AsRef<Path>,
313 options: OpenOptions,
314 password: &EwfPassword,
315 ) -> Result<Self> {
316 let paths = discover_segments(path.as_ref())?;
317 Self::open_segment_paths(paths, options, Some(password))
318 }
319
320 pub fn open_segments<P, I>(paths: I) -> Result<Self>
327 where
328 P: AsRef<Path>,
329 I: IntoIterator<Item = P>,
330 {
331 Self::open_segments_with_options(paths, OpenOptions::default())
332 }
333
334 pub fn open_segments_with_options<P, I>(paths: I, options: OpenOptions) -> Result<Self>
341 where
342 P: AsRef<Path>,
343 I: IntoIterator<Item = P>,
344 {
345 let paths = paths
346 .into_iter()
347 .map(|path| path.as_ref().to_path_buf())
348 .collect();
349 Self::open_segment_paths(paths, options, None)
350 }
351
352 pub fn open_segments_with_options_and_password<P, I>(
362 paths: I,
363 options: OpenOptions,
364 password: &EwfPassword,
365 ) -> Result<Self>
366 where
367 P: AsRef<Path>,
368 I: IntoIterator<Item = P>,
369 {
370 let paths = paths
371 .into_iter()
372 .map(|path| path.as_ref().to_path_buf())
373 .collect();
374 Self::open_segment_paths(paths, options, Some(password))
375 }
376
377 pub fn open_readers<N, R, I>(segments: I) -> Result<Self>
387 where
388 N: Into<PathBuf>,
389 R: SegmentReader + 'static,
390 I: IntoIterator<Item = (N, R)>,
391 {
392 Self::open_readers_with_options(segments, OpenOptions::default())
393 }
394
395 pub fn open_readers_with_options<N, R, I>(segments: I, options: OpenOptions) -> Result<Self>
402 where
403 N: Into<PathBuf>,
404 R: SegmentReader + 'static,
405 I: IntoIterator<Item = (N, R)>,
406 {
407 let mut paths = Vec::new();
408 let mut readers = Vec::new();
409 for (name, reader) in segments {
410 paths.push(name.into());
411 readers.push(Box::new(reader) as SuppliedSegmentReader);
412 }
413 Self::open_segment_readers(paths, readers, options, None)
414 }
415
416 pub fn open_readers_with_options_and_password<N, R, I>(
427 segments: I,
428 options: OpenOptions,
429 password: &EwfPassword,
430 ) -> Result<Self>
431 where
432 N: Into<PathBuf>,
433 R: SegmentReader + 'static,
434 I: IntoIterator<Item = (N, R)>,
435 {
436 let mut paths = Vec::new();
437 let mut readers = Vec::new();
438 for (name, reader) in segments {
439 paths.push(name.into());
440 readers.push(Box::new(reader) as SuppliedSegmentReader);
441 }
442 Self::open_segment_readers(paths, readers, options, Some(password))
443 }
444
445 fn open_segment_paths(
446 paths: Vec<PathBuf>,
447 options: OpenOptions,
448 password: Option<&EwfPassword>,
449 ) -> Result<Self> {
450 if paths.is_empty() {
451 return Err(EwfError::NoSegments("empty segment list".into()));
452 }
453
454 crate::publication::ensure_no_pending(&paths[0])?;
455
456 let statistics = Arc::new(ReaderStatisticsCollector::new(
457 options.reader_statistics_enabled(),
458 ));
459 let segments =
460 SegmentFilePool::new_path(paths.len(), options.maximum_open_handles(), statistics)?;
461 Self::open_segment_sources(paths, segments, options, password, None, None)
462 }
463
464 pub(crate) fn open_acquisition_prefix(
467 paths: Vec<PathBuf>,
468 expected_size: u64,
469 committed_size: u64,
470 ) -> Result<Self> {
471 let options = OpenOptions::default().with_chunk_cache_size(1);
472 let statistics = Arc::new(ReaderStatisticsCollector::new(false));
473 let segments =
474 SegmentFilePool::new_path(paths.len(), options.maximum_open_handles(), statistics)?;
475 Self::open_segment_sources(
476 paths,
477 segments,
478 options,
479 None,
480 None,
481 Some((expected_size, committed_size)),
482 )
483 }
484
485 fn open_segment_readers(
486 paths: Vec<PathBuf>,
487 readers: Vec<SuppliedSegmentReader>,
488 options: OpenOptions,
489 password: Option<&EwfPassword>,
490 ) -> Result<Self> {
491 if paths.is_empty() {
492 return Err(EwfError::NoSegments("empty segment list".into()));
493 }
494 if paths.len() != readers.len() {
495 return Err(EwfError::Malformed(
496 "segment reader count does not match segment labels".into(),
497 ));
498 }
499
500 let statistics = Arc::new(ReaderStatisticsCollector::new(
501 options.reader_statistics_enabled(),
502 ));
503 let segments =
504 SegmentFilePool::new_readers(readers, options.maximum_open_handles(), statistics)?;
505 Self::open_segment_sources(paths, segments, options, password, None, None)
506 }
507
508 pub fn open_sources<N: Into<PathBuf>>(
511 sources: impl IntoIterator<Item = (N, SegmentSource)>,
512 ) -> Result<Self> {
513 Self::open_sources_with_options(sources, OpenOptions::default())
514 }
515
516 pub fn open_sources_with_options<N: Into<PathBuf>>(
519 sources: impl IntoIterator<Item = (N, SegmentSource)>,
520 options: OpenOptions,
521 ) -> Result<Self> {
522 Self::open_positioned_sources(sources, options, None)
523 }
524
525 pub fn open_sources_with_options_and_password<N: Into<PathBuf>>(
527 sources: impl IntoIterator<Item = (N, SegmentSource)>,
528 options: OpenOptions,
529 password: &EwfPassword,
530 ) -> Result<Self> {
531 Self::open_positioned_sources(sources, options, Some(password))
532 }
533
534 fn open_positioned_sources<N: Into<PathBuf>>(
535 sources: impl IntoIterator<Item = (N, SegmentSource)>,
536 options: OpenOptions,
537 password: Option<&EwfPassword>,
538 ) -> Result<Self> {
539 let (paths, sources): (Vec<_>, Vec<_>) = sources
540 .into_iter()
541 .map(|(name, source)| (name.into(), source))
542 .unzip();
543 if paths.is_empty() {
544 return Err(EwfError::NoSegments("empty segment list".into()));
545 }
546 let statistics = Arc::new(ReaderStatisticsCollector::new(
547 options.reader_statistics_enabled(),
548 ));
549 let readers = sources
550 .iter()
551 .map(|source| Box::new(source.cursor()) as SuppliedSegmentReader)
552 .collect();
553 let segments =
554 SegmentFilePool::new_readers(readers, options.maximum_open_handles(), statistics)?;
555 Self::open_segment_sources(paths, segments, options, password, Some(sources), None)
556 }
557
558 fn open_segment_sources(
559 paths: Vec<PathBuf>,
560 mut segments: SegmentFilePool,
561 options: OpenOptions,
562 password: Option<&EwfPassword>,
563 positioned_sources: Option<Vec<SegmentSource>>,
564 acquisition_prefix: Option<(u64, u64)>,
565 ) -> Result<Self> {
566 let statistics = Arc::clone(&segments.statistics);
567 let mut ranges = Vec::new();
568 let mut section_summaries = Vec::new();
569 let mut metadata = EwfMetadata::default();
570 let mut acquisition_errors = Vec::new();
571 let mut memory_extents = Vec::new();
572 let mut single_files = None;
573 let mut ewf2_single_files_tables = SingleFilesAuxTables::default();
574 let mut ewf2_increment_data = Vec::new();
575 let mut ewf2_final_information = None;
576 let mut ewf2_restart_data = None;
577 let mut ewf2_analytical_data = None;
578 let mut sessions = Vec::new();
579 let mut tracks = Vec::new();
580 let mut stored_hashes = StoredHashes::default();
581 let mut media = MediaInfo::default();
582 let mut chunk_size = 0;
583 let mut logical_size = 0;
584 let mut acquisition_complete = true;
585 let mut format = None;
586 let mut format_profile = None;
587 let mut format_profile_hint_only = false;
588 let mut next_ewf1_chunk = 0_u64;
589 let mut discovered_table_chunks = 0_u64;
590 let mut expected_set_identifier: Option<[u8; 16]> = None;
591 let mut expected_ewf1_media = None;
592 let mut expected_ewf1_compression_method = None;
593 let mut expected_ewf2_header_profile = None;
594 let mut expected_ewf2_device_information = None;
595 let mut expected_ewf2_case_data = None;
596 let mut encryption_info = None;
597 let mut encryption_contexts = Vec::with_capacity(paths.len());
598
599 for (segment_index, path) in paths.iter().enumerate() {
600 statistics.record_segment_parse();
601 let (parsed, encryption_context, observed_encryption_info) = {
602 let file = segments.file_mut(segment_index, path)?;
603 let encryption_metadata = ewf1::read_xways_encryption(file.as_reader_mut())?;
604 let observed_encryption_info =
605 encryption_metadata.as_ref().map(EncryptionInfo::from_xways);
606 let encryption_context = encryption_metadata
607 .as_ref()
608 .map(|metadata| {
609 let password = password.ok_or(EwfError::PasswordRequired)?;
610 EncryptionContext::derive(metadata, password)
611 })
612 .transpose()?;
613 let parsed = parse_segment(
614 file.as_reader_mut(),
615 &paths[0],
616 segment_index,
617 Ewf1SegmentContext {
618 first_chunk: next_ewf1_chunk,
619 inherited_media_geometry: expected_ewf1_media,
620 },
621 options.strictness(),
622 options.header_codepage(),
623 &statistics,
624 )?;
625 (parsed, encryption_context, observed_encryption_info)
626 };
627 if segment_index == 0 {
628 encryption_info = observed_encryption_info;
629 } else if observed_encryption_info != encryption_info {
630 return Err(EwfError::Malformed(
631 "X-Ways EWF1 encryption settings differ across segments".into(),
632 ));
633 }
634 encryption_contexts.push(encryption_context);
635 section_summaries.extend(parsed.sections);
636 let expected_segment_number = u64::try_from(segment_index + 1)
637 .map_err(|_| EwfError::Malformed("segment index overflow".into()))?;
638 if parsed.segment_number != expected_segment_number {
639 return Err(EwfError::Malformed(format!(
640 "segment {} declares segment number {}, expected {}",
641 segment_index + 1,
642 parsed.segment_number,
643 expected_segment_number
644 )));
645 }
646 validate_set_identifier(&mut expected_set_identifier, parsed.set_identifier)?;
647 if let Some(observed) = parsed.ewf1_compression_method {
648 if expected_ewf1_compression_method.is_some_and(|expected| expected != observed) {
649 return Err(EwfError::Malformed(
650 "EWF1 compression method mismatch across segments".into(),
651 ));
652 }
653 expected_ewf1_compression_method.get_or_insert(observed);
654 }
655 if let Some(observed) = parsed.ewf1_media_geometry {
656 if expected_ewf1_media
657 .as_ref()
658 .is_some_and(|expected| expected != &observed)
659 {
660 return Err(EwfError::Malformed(
661 "EWF1 media geometry mismatch across segments".into(),
662 ));
663 }
664 expected_ewf1_media.get_or_insert(observed);
665 }
666 validate_ewf2_header_profile(
667 &mut expected_ewf2_header_profile,
668 parsed.ewf2_header_profile,
669 )?;
670 if let Some(device_information) = parsed.ewf2_device_information.as_deref() {
671 remember_ewf2_metadata_payload(
672 &mut expected_ewf2_device_information,
673 device_information,
674 "device information",
675 )?;
676 }
677 if let Some(case_data) = parsed.ewf2_case_data.as_deref() {
678 remember_ewf2_metadata_payload(
679 &mut expected_ewf2_case_data,
680 case_data,
681 "case data",
682 )?;
683 }
684 if segment_index == 0 {
685 chunk_size = parsed.chunk_size;
686 logical_size = parsed.logical_size;
687 acquisition_complete = parsed.acquisition_complete;
688 media = parsed.media;
689 metadata = parsed.metadata;
690 acquisition_errors = parsed.acquisition_errors;
691 memory_extents = parsed.memory_extents;
692 single_files = parsed.single_files;
693 ewf2_single_files_tables = parsed.ewf2_single_files_tables;
694 ewf2_increment_data = parsed.ewf2_increment_data;
695 ewf2_final_information = parsed.ewf2_final_information;
696 ewf2_restart_data = parsed.ewf2_restart_data;
697 ewf2_analytical_data = parsed.ewf2_analytical_data;
698 sessions = parsed.sessions;
699 tracks = parsed.tracks;
700 format = Some(parsed.format);
701 format_profile = Some(parsed.format_profile);
702 format_profile_hint_only = parsed.format_profile_hint_only;
703 } else {
704 merge_segment_format_profile(
705 &mut format_profile,
706 &mut format_profile_hint_only,
707 parsed.format_profile,
708 parsed.format_profile_hint_only,
709 )?;
710 merge_acquisition_errors(&mut acquisition_errors, parsed.acquisition_errors)?;
711 memory_extents.extend(parsed.memory_extents);
712 merge_single_files(&mut single_files, parsed.single_files)?;
713 merge_single_files_aux_tables(
714 &mut ewf2_single_files_tables,
715 parsed.ewf2_single_files_tables,
716 )?;
717 ewf2_increment_data.extend(parsed.ewf2_increment_data);
718 merge_optional_ewf2_raw_section(
719 &mut ewf2_final_information,
720 parsed.ewf2_final_information,
721 "final information",
722 )?;
723 merge_optional_ewf2_string_section(
724 &mut ewf2_restart_data,
725 parsed.ewf2_restart_data,
726 "restart data",
727 )?;
728 merge_optional_ewf2_string_section(
729 &mut ewf2_analytical_data,
730 parsed.ewf2_analytical_data,
731 "analytical data",
732 )?;
733 sessions.extend(parsed.sessions);
734 tracks.extend(parsed.tracks);
735 acquisition_complete = parsed.acquisition_complete;
736 }
737 merge_hashes(&mut stored_hashes, &parsed.stored_hashes)?;
738 if parsed.format == Format::Ewf1 {
739 next_ewf1_chunk = next_ewf1_chunk
740 .checked_add(parsed.table_chunk_count)
741 .ok_or_else(|| EwfError::Malformed("EWF1 chunk count overflow".into()))?;
742 }
743 for range in &parsed.ranges {
744 discovered_table_chunks = discovered_table_chunks.max(
745 range
746 .first_chunk
747 .checked_add(range.chunk_count)
748 .ok_or_else(|| {
749 EwfError::Malformed("table range chunk count overflow".into())
750 })?,
751 );
752 }
753 ranges.extend(parsed.ranges);
754 }
755
756 let format = format.ok_or_else(|| EwfError::Malformed("image has no segments".into()))?;
757 if format == Format::Ewf1
758 && matches!(
759 format_profile,
760 Some(
761 FormatProfile::LogicalEnCase5
762 | FormatProfile::LogicalEnCase6
763 | FormatProfile::LogicalEnCase7
764 )
765 )
766 && let Some(catalog) = single_files.as_ref()
767 {
768 logical_size = catalog.data_size;
769 }
770 if format == Format::Ewf2
771 && expected_ewf2_device_information.is_none()
772 && expected_ewf2_case_data.is_none()
773 {
774 return Err(EwfError::Malformed(
775 "missing EWF2 device information or case data section".into(),
776 ));
777 }
778
779 if logical_size == 0 && chunk_size > 0 {
780 logical_size = chunk_size
781 .checked_mul(discovered_table_chunks)
782 .ok_or_else(|| EwfError::Malformed("logical size overflow".into()))?;
783 }
784 if discovered_table_chunks > 0 {
785 media.chunk_count = Some(discovered_table_chunks);
786 }
787 if let Some((expected_size, committed_size)) = acquisition_prefix {
788 if format != Format::Ewf1
789 || logical_size != expected_size
790 || committed_size == 0
791 || committed_size > expected_size
792 {
793 return Err(EwfError::Malformed(
794 "invalid acquisition prefix geometry".into(),
795 ));
796 }
797 logical_size = committed_size;
798 }
799
800 let info = ImageInfo {
801 format,
802 format_profile: format_profile.unwrap_or_default(),
803 segment_count: paths.len(),
804 segment_paths: paths,
805 chunk_size,
806 logical_size,
807 acquisition_complete,
808 header_codepage: options.header_codepage(),
809 header_values_date_format: options.header_values_date_format(),
810 media,
811 metadata,
812 stored_hashes,
813 acquisition_errors,
814 memory_extents,
815 single_files,
816 ewf2_single_files_tables,
817 ewf2_increment_data,
818 ewf2_final_information,
819 ewf2_restart_data,
820 ewf2_analytical_data,
821 sessions,
822 tracks,
823 };
824 let index = LazyChunkIndex::new(ranges, info.logical_size, info.chunk_size)?;
825 let chunk_size = usize::try_from(info.chunk_size)
826 .map_err(|_| EwfError::Malformed("chunk size does not fit usize".into()))?;
827 let cache_entries = match options.chunk_cache_capacity() {
828 ChunkCacheCapacity::Chunks(entries) => entries.max(1),
829 ChunkCacheCapacity::Bytes(bytes) => bytes.checked_div(chunk_size).unwrap_or(0).max(1),
830 };
831 let cache_size =
832 NonZeroUsize::new(cache_entries).expect("chunk cache size is at least one");
833 let chunk_cache_capacity_bytes = u64::try_from(cache_entries)
834 .unwrap_or(u64::MAX)
835 .saturating_mul(info.chunk_size);
836
837 let image = Self {
838 inner: Arc::new(ImageInner {
839 info,
840 sections: section_summaries,
841 open_strictness: options.strictness(),
842 encryption_info,
843 encryption_contexts,
844 segments: Mutex::new(segments),
845 segment_handle_available: Condvar::new(),
846 positioned_sources,
847 index,
848 chunk_cache: Mutex::new(LruCache::new(cache_size)),
849 chunk_in_flight: Mutex::new(HashMap::new()),
850 chunk_cache_capacity_bytes,
851 table_page_cache: Mutex::new(TablePageCache::new(
852 options.table_entry_cache_size_bytes(),
853 )),
854 statistics,
855 checksum_errors: Mutex::new(Vec::new()),
856 read_zero_chunk_on_error: AtomicBool::new(options.read_zero_chunk_on_error()),
857 abort_signaled: AtomicBool::new(false),
858 }),
859 };
860 if image
861 .encryption_info()
862 .is_some_and(|info| !info.password_verifier_present())
863 && image.info().logical_size > 0
864 {
865 image.read_data_chunk(0)?;
866 }
867 Ok(image)
868 }
869
870 pub fn info(&self) -> &ImageInfo {
872 &self.inner.info
873 }
874
875 pub fn sections(&self) -> &[SectionInfo] {
878 &self.inner.sections
879 }
880
881 pub fn open_strictness(&self) -> OpenStrictness {
883 self.inner.open_strictness
884 }
885
886 #[must_use]
888 pub fn encryption_info(&self) -> Option<EncryptionInfo> {
889 self.inner.encryption_info
890 }
891
892 pub fn reader_statistics(&self) -> Option<ReaderStatistics> {
896 self.inner.statistics.snapshot()
897 }
898
899 pub fn reader_cache_info(&self) -> ReaderCacheInfo {
901 let cache = self
902 .inner
903 .table_page_cache
904 .lock()
905 .unwrap_or_else(std::sync::PoisonError::into_inner);
906 ReaderCacheInfo::new(
907 self.inner.chunk_cache_capacity_bytes,
908 cache.capacity_bytes(),
909 cache.cached_bytes(),
910 cache.peak_bytes(),
911 )
912 }
913
914 pub fn filename(&self) -> &Path {
916 self.inner.info.segment_paths[0].as_path()
917 }
918
919 pub fn number_of_segments(&self) -> usize {
921 self.inner.info.segment_count
922 }
923
924 pub fn maximum_number_of_open_handles(&self) -> Result<Option<usize>> {
930 Ok(self
931 .inner
932 .segments
933 .lock()
934 .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
935 .maximum_open_handles())
936 }
937
938 pub fn set_maximum_number_of_open_handles(
949 &self,
950 maximum_open_handles: Option<usize>,
951 ) -> Result<()> {
952 self.inner
953 .segments
954 .lock()
955 .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
956 .set_maximum_open_handles(maximum_open_handles)
957 }
958
959 pub fn number_of_open_segment_handles(&self) -> Result<usize> {
965 Ok(self
966 .inner
967 .segments
968 .lock()
969 .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
970 .open_count())
971 }
972
973 pub fn segment_filenames(&self) -> &[PathBuf] {
975 &self.inner.info.segment_paths
976 }
977
978 pub fn segment_filename(&self, index: usize) -> Option<&Path> {
980 self.inner
981 .info
982 .segment_paths
983 .get(index)
984 .map(PathBuf::as_path)
985 }
986
987 pub fn segment_set_size(&self) -> Result<u64> {
996 let paths = self.inner.info.segment_paths.clone();
997 let mut segments = self
998 .inner
999 .segments
1000 .lock()
1001 .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?;
1002 let mut size = 0_u64;
1003 for (segment_index, path) in paths.iter().enumerate() {
1004 size = size
1005 .checked_add(
1006 segments
1007 .file_mut(segment_index, path)?
1008 .as_reader_mut()
1009 .segment_len()?,
1010 )
1011 .ok_or_else(|| EwfError::Malformed("segment set size overflow".into()))?;
1012 }
1013 Ok(size)
1014 }
1015
1016 pub fn segment_files_corrupted(&self) -> Result<bool> {
1025 if self.has_supplied_segment_readers()? {
1026 return Ok(false);
1027 }
1028 check_segment_files_corruption(self.segment_filenames())
1029 }
1030
1031 pub fn segment_files_encrypted(&self) -> Result<bool> {
1040 if self.has_supplied_segment_readers()? {
1041 return Ok(false);
1042 }
1043 check_segment_files_encryption(self.segment_filenames())
1044 }
1045
1046 pub fn segment_filename_for_chunk(&self, chunk_index: u64) -> Result<&Path> {
1053 let chunk = self.lookup_chunk(chunk_index)?;
1054 self.segment_filename(chunk.segment_index)
1055 .ok_or_else(|| EwfError::Malformed("chunk references missing segment".into()))
1056 }
1057
1058 pub fn segment_filename_for_offset(&self, offset: u64) -> Result<Option<&Path>> {
1066 if offset >= self.inner.info.logical_size {
1067 return Ok(None);
1068 }
1069 let chunk_size = self.inner.info.chunk_size;
1070 if chunk_size == 0 {
1071 return Err(EwfError::Malformed("chunk size is zero".into()));
1072 }
1073 self.segment_filename_for_chunk(offset / chunk_size)
1074 .map(Some)
1075 }
1076
1077 pub fn format(&self) -> Format {
1079 self.inner.info.format
1080 }
1081
1082 pub fn format_profile(&self) -> FormatProfile {
1084 self.inner.info.format_profile
1085 }
1086
1087 pub fn chunk_size(&self) -> u64 {
1089 self.inner.info.chunk_size
1090 }
1091
1092 pub fn media_size(&self) -> u64 {
1094 self.inner.info.logical_size
1095 }
1096
1097 pub fn header_codepage(&self) -> HeaderCodepage {
1099 self.inner.info.header_codepage
1100 }
1101
1102 pub fn header_values_date_format(&self) -> HeaderDateFormat {
1104 self.inner.info.header_values_date_format
1105 }
1106
1107 pub fn sectors_per_chunk(&self) -> Option<u64> {
1109 self.inner.info.media.sectors_per_chunk
1110 }
1111
1112 pub fn bytes_per_sector(&self) -> Option<u64> {
1114 self.inner.info.media.bytes_per_sector
1115 }
1116
1117 pub fn number_of_sectors(&self) -> Option<u64> {
1119 self.inner.info.media.sector_count
1120 }
1121
1122 pub fn number_of_chunks(&self) -> Option<u64> {
1124 self.inner.info.media.chunk_count
1125 }
1126
1127 pub fn error_granularity(&self) -> Option<u64> {
1129 self.inner.info.media.error_granularity
1130 }
1131
1132 pub fn segment_file_set_identifier(&self) -> Option<[u8; 16]> {
1134 self.inner.info.media.set_identifier
1135 }
1136
1137 pub fn segment_file_version(&self) -> Option<SegmentFileVersion> {
1139 self.inner.info.media.ewf2_segment_file_version
1140 }
1141
1142 pub fn compression_method(&self) -> Option<CompressionMethod> {
1144 self.inner.info.media.compression_method
1145 }
1146
1147 pub fn compression_values(&self) -> CompressionValues {
1149 self.inner.info.media.compression_values
1150 }
1151
1152 pub fn media_type(&self) -> Option<MediaType> {
1154 self.inner.info.media.media_type
1155 }
1156
1157 pub fn media_flags(&self) -> MediaFlags {
1159 self.inner.info.media.media_flags
1160 }
1161
1162 pub fn memory_extents(&self) -> &[MemoryExtent] {
1164 &self.inner.info.memory_extents
1165 }
1166
1167 pub fn number_of_memory_extents(&self) -> usize {
1169 self.inner.info.memory_extents.len()
1170 }
1171
1172 pub fn memory_extent(&self, index: usize) -> Option<&MemoryExtent> {
1174 self.inner.info.memory_extents.get(index)
1175 }
1176
1177 pub fn ewf2_increment_data(&self) -> &[Vec<u8>] {
1179 &self.inner.info.ewf2_increment_data
1180 }
1181
1182 pub fn number_of_ewf2_increment_data_sections(&self) -> usize {
1184 self.inner.info.ewf2_increment_data.len()
1185 }
1186
1187 pub fn ewf2_increment_data_section(&self, index: usize) -> Option<&[u8]> {
1189 self.inner
1190 .info
1191 .ewf2_increment_data
1192 .get(index)
1193 .map(Vec::as_slice)
1194 }
1195
1196 pub fn ewf2_final_information(&self) -> Option<&[u8]> {
1198 self.inner.info.ewf2_final_information.as_deref()
1199 }
1200
1201 pub fn ewf2_restart_data(&self) -> Option<&str> {
1203 self.inner.info.ewf2_restart_data.as_deref()
1204 }
1205
1206 pub fn ewf2_analytical_data(&self) -> Option<&str> {
1208 self.inner.info.ewf2_analytical_data.as_deref()
1209 }
1210
1211 pub fn header_value(&self, identifier: &str) -> Option<std::borrow::Cow<'_, str>> {
1213 self.inner
1214 .info
1215 .metadata
1216 .header_value_with_date_format(identifier, self.inner.info.header_values_date_format)
1217 }
1218
1219 pub fn number_of_header_values(&self) -> usize {
1221 self.inner.info.metadata.number_of_header_values()
1222 }
1223
1224 pub fn header_value_identifier(&self, index: usize) -> Option<&str> {
1226 self.inner.info.metadata.header_value_identifier(index)
1227 }
1228
1229 pub fn hash_value(&self, identifier: &str) -> Option<&str> {
1231 self.inner.info.stored_hashes.hash_value(identifier)
1232 }
1233
1234 pub fn number_of_hash_values(&self) -> usize {
1236 self.inner.info.stored_hashes.number_of_hash_values()
1237 }
1238
1239 pub fn hash_value_identifier(&self, index: usize) -> Option<&str> {
1241 self.inner.info.stored_hashes.hash_value_identifier(index)
1242 }
1243
1244 pub fn cursor(&self) -> ImageCursor {
1246 self.inner.statistics.record_cursor_created();
1247 ImageCursor {
1248 image: self.clone(),
1249 position: 0,
1250 }
1251 }
1252
1253 pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
1263 self.read_at_impl(buf, offset, false)
1264 }
1265
1266 fn read_at_impl(&self, buf: &mut [u8], offset: u64, strict: bool) -> Result<usize> {
1267 self.ensure_not_aborted()?;
1268 if buf.is_empty() || offset >= self.inner.info.logical_size {
1269 return Ok(0);
1270 }
1271
1272 let chunk_size = self.inner.info.chunk_size;
1273 if chunk_size == 0 {
1274 return Err(EwfError::Malformed("chunk size is zero".into()));
1275 }
1276
1277 let available = self.inner.info.logical_size - offset;
1278 let requested = u64::try_from(buf.len())
1279 .map_err(|_| EwfError::Malformed("read buffer length does not fit u64".into()))?;
1280 let to_read = available.min(requested);
1281 let mut copied = 0_usize;
1282 let mut current = offset;
1283
1284 while u64::try_from(copied).expect("usize fits u64") < to_read {
1285 self.ensure_not_aborted()?;
1286 let chunk_id = current / chunk_size;
1287 let page_offset = usize::try_from(current % chunk_size)
1288 .map_err(|_| EwfError::Malformed("page offset does not fit usize".into()))?;
1289 let decoded = if strict {
1290 Arc::new(self.verification_chunk(chunk_id)?)
1291 } else {
1292 self.read_chunk(chunk_id)?
1293 };
1294 let remaining =
1295 usize::try_from(to_read - u64::try_from(copied).unwrap()).map_err(|_| {
1296 EwfError::Malformed("remaining read size does not fit usize".into())
1297 })?;
1298 let page_available = decoded.len().saturating_sub(page_offset);
1299 let n = remaining.min(page_available);
1300 if n == 0 {
1301 break;
1302 }
1303
1304 buf[copied..copied + n].copy_from_slice(&decoded[page_offset..page_offset + n]);
1305 copied += n;
1306 current += u64::try_from(n).expect("usize fits u64");
1307 }
1308
1309 Ok(copied)
1310 }
1311
1312 pub fn read_buffer_at_offset(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
1318 self.read_at(buf, offset)
1319 }
1320
1321 pub fn read_single_file_at(
1332 &self,
1333 entry: &SingleFileEntry,
1334 buf: &mut [u8],
1335 offset: u64,
1336 ) -> Result<usize> {
1337 self.read_single_file_at_impl(entry, buf, offset, false)
1338 }
1339
1340 pub fn read_single_file_at_strict(
1344 &self,
1345 entry: &SingleFileEntry,
1346 buf: &mut [u8],
1347 offset: u64,
1348 ) -> Result<usize> {
1349 self.read_single_file_at_impl(entry, buf, offset, true)
1350 }
1351
1352 fn read_single_file_at_impl(
1353 &self,
1354 entry: &SingleFileEntry,
1355 buf: &mut [u8],
1356 offset: u64,
1357 strict: bool,
1358 ) -> Result<usize> {
1359 self.ensure_not_aborted()?;
1360 if buf.is_empty() {
1361 return Ok(0);
1362 }
1363
1364 let file_size = single_file_size(entry)?;
1365 if offset >= file_size {
1366 return Ok(0);
1367 }
1368
1369 let requested = u64::try_from(buf.len())
1370 .map_err(|_| EwfError::Malformed("read buffer length does not fit u64".into()))?;
1371 let to_read = requested.min(file_size - offset);
1372 let mut copied = 0_usize;
1373 let mut file_position = 0_u64;
1374 let ewf1_logical_sparse = self.inner.info.format == Format::Ewf1
1375 && matches!(
1376 self.inner.info.format_profile,
1377 FormatProfile::LogicalEnCase5
1378 | FormatProfile::LogicalEnCase6
1379 | FormatProfile::LogicalEnCase7
1380 )
1381 && entry
1382 .flags
1383 .is_some_and(|flags| flags & EWF1_LOGICAL_SPARSE_FLAG != 0);
1384
1385 if (entry.extents.is_empty() || ewf1_logical_sparse)
1386 && let Some(duplicate_data_offset) = entry.duplicate_data_offset
1387 && duplicate_data_offset >= 0
1388 {
1389 let duplicate_data_offset = u64::try_from(duplicate_data_offset).map_err(|_| {
1390 EwfError::Malformed("single file duplicate data offset does not fit u64".into())
1391 })?;
1392 let image_offset = duplicate_data_offset.checked_add(offset).ok_or_else(|| {
1393 EwfError::Malformed("single file duplicate data offset overflow".into())
1394 })?;
1395 let read_size = usize::try_from(to_read).map_err(|_| {
1396 EwfError::Malformed("single file duplicate read size does not fit usize".into())
1397 })?;
1398 let out = &mut buf[..read_size];
1399 let read = self.read_at_impl(out, image_offset, strict)?;
1400 if read != read_size {
1401 return Err(EwfError::Malformed(
1402 "single file duplicate data read was truncated".into(),
1403 ));
1404 }
1405 return Ok(read);
1406 }
1407
1408 if ewf1_logical_sparse {
1409 let extent = entry
1410 .extents
1411 .first()
1412 .filter(|extent| !extent.sparse && extent.data_size >= 1)
1413 .ok_or_else(|| {
1414 EwfError::Malformed("single file sparse entry has no stored byte".into())
1415 })?;
1416 let mut byte = [0_u8; 1];
1417 if self.read_at_impl(&mut byte, extent.data_offset, strict)? != 1 {
1418 return Err(EwfError::Malformed(
1419 "single file sparse entry stored byte was truncated".into(),
1420 ));
1421 }
1422 let read_size = usize::try_from(to_read).map_err(|_| {
1423 EwfError::Malformed("single file sparse read size does not fit usize".into())
1424 })?;
1425 buf[..read_size].fill(byte[0]);
1426 return Ok(read_size);
1427 }
1428
1429 for extent in &entry.extents {
1430 let extent_start = file_position;
1431 let extent_end = extent_start
1432 .checked_add(extent.data_size)
1433 .ok_or_else(|| EwfError::Malformed("single file extent range overflow".into()))?;
1434 file_position = extent_end;
1435
1436 let read_start = offset.max(extent_start);
1437 let read_end = (offset + to_read).min(extent_end);
1438 if read_start >= read_end {
1439 continue;
1440 }
1441
1442 let extent_relative_offset = read_start
1443 .checked_sub(extent_start)
1444 .ok_or_else(|| EwfError::Malformed("single file extent offset underflow".into()))?;
1445 let output_offset = usize::try_from(read_start - offset).map_err(|_| {
1446 EwfError::Malformed("single file output offset does not fit usize".into())
1447 })?;
1448 let read_size = usize::try_from(read_end - read_start).map_err(|_| {
1449 EwfError::Malformed("single file read size does not fit usize".into())
1450 })?;
1451 let out = &mut buf[output_offset..output_offset + read_size];
1452
1453 if extent.sparse {
1454 out.fill(0);
1455 copied += read_size;
1456 continue;
1457 }
1458
1459 let image_offset = extent
1460 .data_offset
1461 .checked_add(extent_relative_offset)
1462 .ok_or_else(|| {
1463 EwfError::Malformed("single file extent data offset overflow".into())
1464 })?;
1465 let read = self.read_at_impl(out, image_offset, strict)?;
1466 if read != read_size {
1467 return Err(EwfError::Malformed(
1468 "single file extent read was truncated".into(),
1469 ));
1470 }
1471 copied += read_size;
1472 }
1473
1474 if u64::try_from(copied).expect("usize fits u64") != to_read {
1475 return Err(EwfError::Malformed(
1476 "single file extents do not cover requested range".into(),
1477 ));
1478 }
1479 Ok(copied)
1480 }
1481
1482 pub fn read_file_entry_at(
1488 &self,
1489 entry: &SingleFileEntry,
1490 buf: &mut [u8],
1491 offset: u64,
1492 ) -> Result<usize> {
1493 self.read_single_file_at(entry, buf, offset)
1494 }
1495
1496 pub fn root_file_entry(&self) -> Option<&SingleFileEntry> {
1498 self.inner
1499 .info
1500 .single_files
1501 .as_ref()
1502 .map(|single_files| &single_files.root)
1503 }
1504
1505 pub fn file_entry_by_path(&self, path: &str) -> Result<Option<&SingleFileEntry>> {
1511 self.inner
1512 .info
1513 .single_files
1514 .as_ref()
1515 .map_or(Ok(None), |single_files| single_files.entry_by_path(path))
1516 }
1517
1518 pub fn source_for_file_entry(&self, entry: &SingleFileEntry) -> Option<&SingleFileSource> {
1520 self.inner
1521 .info
1522 .single_files
1523 .as_ref()?
1524 .source_for_entry(entry)
1525 }
1526
1527 pub fn subject_for_file_entry(&self, entry: &SingleFileEntry) -> Option<&SingleFileSubject> {
1529 self.inner
1530 .info
1531 .single_files
1532 .as_ref()?
1533 .subject_for_entry(entry)
1534 }
1535
1536 pub fn access_control_entries_for_file_entry(
1538 &self,
1539 entry: &SingleFileEntry,
1540 ) -> &[SingleFilePermission] {
1541 self.inner
1542 .info
1543 .single_files
1544 .as_ref()
1545 .map_or(&[], |single_files| {
1546 single_files.access_control_entries_for_entry(entry)
1547 })
1548 }
1549
1550 pub fn number_of_access_control_entries_for_file_entry(
1552 &self,
1553 entry: &SingleFileEntry,
1554 ) -> usize {
1555 self.access_control_entries_for_file_entry(entry).len()
1556 }
1557
1558 pub fn access_control_entry_for_file_entry(
1560 &self,
1561 entry: &SingleFileEntry,
1562 index: usize,
1563 ) -> Option<&SingleFilePermission> {
1564 self.access_control_entries_for_file_entry(entry).get(index)
1565 }
1566
1567 pub fn md5_hash(&self) -> Option<[u8; 16]> {
1569 self.inner.info.stored_hashes.md5
1570 }
1571
1572 pub fn sha1_hash(&self) -> Option<[u8; 20]> {
1574 self.inner.info.stored_hashes.sha1
1575 }
1576
1577 pub fn acquisition_errors(&self) -> &[AcquisitionError] {
1579 &self.inner.info.acquisition_errors
1580 }
1581
1582 pub fn number_of_acquisition_errors(&self) -> usize {
1584 self.inner.info.acquisition_errors.len()
1585 }
1586
1587 pub fn acquisition_error(&self, index: usize) -> Option<&AcquisitionError> {
1589 self.inner.info.acquisition_errors.get(index)
1590 }
1591
1592 pub fn sessions(&self) -> &[SectorRange] {
1594 &self.inner.info.sessions
1595 }
1596
1597 pub fn number_of_sessions(&self) -> usize {
1599 self.inner.info.sessions.len()
1600 }
1601
1602 pub fn session(&self, index: usize) -> Option<&SectorRange> {
1604 self.inner.info.sessions.get(index)
1605 }
1606
1607 pub fn tracks(&self) -> &[SectorRange] {
1609 &self.inner.info.tracks
1610 }
1611
1612 pub fn number_of_tracks(&self) -> usize {
1614 self.inner.info.tracks.len()
1615 }
1616
1617 pub fn track(&self, index: usize) -> Option<&SectorRange> {
1619 self.inner.info.tracks.get(index)
1620 }
1621
1622 pub fn read_zero_chunk_on_error(&self) -> bool {
1624 self.inner.read_zero_chunk_on_error.load(Ordering::Relaxed)
1625 }
1626
1627 pub fn set_read_zero_chunk_on_error(&self, zero_on_error: bool) {
1629 self.inner
1630 .read_zero_chunk_on_error
1631 .store(zero_on_error, Ordering::Relaxed);
1632 }
1633
1634 pub fn signal_abort(&self) {
1636 self.inner.abort_signaled.store(true, Ordering::Relaxed);
1637 }
1638
1639 pub fn checksum_errors(&self) -> Result<Vec<SectorRange>> {
1645 Ok(self
1646 .inner
1647 .checksum_errors
1648 .lock()
1649 .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?
1650 .clone())
1651 }
1652
1653 pub fn number_of_checksum_errors(&self) -> Result<usize> {
1659 Ok(self
1660 .inner
1661 .checksum_errors
1662 .lock()
1663 .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?
1664 .len())
1665 }
1666
1667 pub fn checksum_error(&self, index: usize) -> Result<Option<SectorRange>> {
1673 Ok(self
1674 .inner
1675 .checksum_errors
1676 .lock()
1677 .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?
1678 .get(index)
1679 .cloned())
1680 }
1681
1682 pub fn single_file_cursor(&self, entry: &SingleFileEntry) -> SingleFileCursor {
1684 self.inner.statistics.record_cursor_created();
1685 SingleFileCursor {
1686 image: self.clone(),
1687 entry: entry.clone(),
1688 position: 0,
1689 }
1690 }
1691
1692 pub fn single_file_cursor_by_path(&self, path: &str) -> Result<Option<SingleFileCursor>> {
1698 self.file_entry_by_path(path)
1699 .map(|entry| entry.map(|entry| self.single_file_cursor(entry)))
1700 }
1701
1702 pub fn read_data_chunk(&self, chunk_index: u64) -> Result<DataChunk> {
1710 self.ensure_not_aborted()?;
1711 let chunk = self.lookup_chunk(chunk_index)?;
1712 let (data, corrupted) = self.decode_chunk_with_policy(chunk_index, chunk)?;
1713 let logical_offset = chunk_index
1714 .checked_mul(self.inner.info.chunk_size)
1715 .ok_or_else(|| EwfError::Malformed("data chunk logical offset overflow".into()))?;
1716
1717 Ok(DataChunk {
1718 chunk_index,
1719 logical_offset,
1720 logical_size: chunk.logical_size,
1721 encoded_size: chunk.encoded_size,
1722 encoding: data_chunk_encoding(chunk.encoding),
1723 corrupted,
1724 data,
1725 })
1726 }
1727
1728 pub fn read_encoded_data_chunk(&self, chunk_index: u64) -> Result<EncodedDataChunk> {
1735 self.ensure_not_aborted()?;
1736 let chunk = self.lookup_chunk(chunk_index)?;
1737 let data = self.read_encoded_chunk_bytes(chunk_index, chunk)?;
1738 let logical_offset = chunk_index
1739 .checked_mul(self.inner.info.chunk_size)
1740 .ok_or_else(|| EwfError::Malformed("data chunk logical offset overflow".into()))?;
1741
1742 Ok(EncodedDataChunk {
1743 chunk_index,
1744 logical_offset,
1745 logical_size: chunk.logical_size,
1746 encoded_size: chunk.encoded_size,
1747 encoding: data_chunk_encoding(chunk.encoding),
1748 has_checksum: chunk.validate_checksum,
1749 data,
1750 })
1751 }
1752
1753 fn read_chunk(&self, chunk_id: u64) -> Result<Arc<Vec<u8>>> {
1754 self.ensure_not_aborted()?;
1755 let mut first_cache_check = true;
1756 loop {
1757 let cached = self
1758 .inner
1759 .chunk_cache
1760 .lock()
1761 .map_err(|_| EwfError::Malformed("chunk cache lock poisoned".into()))?
1762 .get(&chunk_id)
1763 .cloned();
1764 if first_cache_check {
1765 self.inner
1766 .statistics
1767 .record_chunk_cache_access(cached.is_some());
1768 first_cache_check = false;
1769 }
1770 if let Some(cached) = cached {
1771 return Ok(cached);
1772 }
1773
1774 let (flight, decode_here) = {
1775 let mut in_flight = self
1776 .inner
1777 .chunk_in_flight
1778 .lock()
1779 .map_err(|_| EwfError::Malformed("chunk flight lock poisoned".into()))?;
1780 if let Some(flight) = in_flight.get(&chunk_id) {
1781 (Arc::clone(flight), false)
1782 } else {
1783 let flight = Arc::new(ChunkFlight {
1784 complete: Mutex::new(false),
1785 ready: Condvar::new(),
1786 });
1787 in_flight.insert(chunk_id, Arc::clone(&flight));
1788 (flight, true)
1789 }
1790 };
1791
1792 if !decode_here {
1793 self.inner.statistics.record_chunk_cache_coalesced();
1794 let mut complete = flight
1795 .complete
1796 .lock()
1797 .map_err(|_| EwfError::Malformed("chunk flight lock poisoned".into()))?;
1798 while !*complete {
1799 complete = flight
1800 .ready
1801 .wait(complete)
1802 .map_err(|_| EwfError::Malformed("chunk flight lock poisoned".into()))?;
1803 }
1804 continue;
1805 }
1806
1807 let decoded = (|| {
1808 let chunk = self.lookup_chunk(chunk_id)?;
1809 let (decoded, _) = self.decode_chunk_with_policy(chunk_id, chunk)?;
1810 let decoded = Arc::new(decoded);
1811 self.inner
1812 .chunk_cache
1813 .lock()
1814 .map_err(|_| EwfError::Malformed("chunk cache lock poisoned".into()))?
1815 .put(chunk_id, Arc::clone(&decoded));
1816 Ok(decoded)
1817 })();
1818 let mut complete = flight
1819 .complete
1820 .lock()
1821 .map_err(|_| EwfError::Malformed("chunk flight lock poisoned".into()))?;
1822 *complete = true;
1823 flight.ready.notify_all();
1824 drop(complete);
1825 self.inner
1826 .chunk_in_flight
1827 .lock()
1828 .map_err(|_| EwfError::Malformed("chunk flight lock poisoned".into()))?
1829 .remove(&chunk_id);
1830 return decoded;
1831 }
1832 }
1833
1834 fn lookup_chunk(&self, chunk_id: u64) -> Result<Chunk> {
1835 let (_, range) = self.inner.index.range_index_for(chunk_id)?;
1836 let local_index = chunk_id
1837 .checked_sub(range.first_chunk)
1838 .ok_or_else(|| EwfError::Malformed("chunk range underflow".into()))?;
1839 let logical_size = logical_chunk_size(
1840 self.inner.info.logical_size,
1841 self.inner.info.chunk_size,
1842 chunk_id,
1843 )?;
1844
1845 match range.kind {
1846 TableRangeKind::Ewf1 => self.lookup_ewf1_chunk(range, local_index, logical_size),
1847 TableRangeKind::Ewf2 => self.lookup_ewf2_chunk(range, local_index, logical_size),
1848 }
1849 }
1850
1851 fn lookup_ewf1_chunk(
1852 &self,
1853 range: &TableRange,
1854 local_index: u64,
1855 logical_size: usize,
1856 ) -> Result<Chunk> {
1857 let raw = self.read_u32_at(
1858 range.segment_index,
1859 table_entry_offset(range, local_index, 4)?,
1860 )?;
1861 let next_raw = if local_index + 1 < range.chunk_count {
1862 Some(self.read_u32_at(
1863 range.segment_index,
1864 table_entry_offset(range, local_index + 1, 4)?,
1865 )?)
1866 } else {
1867 None
1868 };
1869 let entry = decode_ewf1_entry(
1870 range,
1871 raw,
1872 self.inner.info.chunk_size,
1873 next_raw,
1874 local_index + 1 == range.chunk_count,
1875 )?;
1876
1877 let next_offset = if let Some(next_raw) = next_raw {
1878 decode_ewf1_entry(
1879 range,
1880 next_raw,
1881 self.inner.info.chunk_size,
1882 None,
1883 local_index + 2 == range.chunk_count,
1884 )?
1885 .offset
1886 } else {
1887 range
1888 .data_end
1889 .ok_or_else(|| EwfError::Malformed("EWF1 table range has no data end".into()))?
1890 };
1891
1892 if next_offset <= entry.offset {
1893 return Err(EwfError::Malformed(
1894 "EWF1 chunk offsets are not ordered".into(),
1895 ));
1896 }
1897 let encoded_size = next_offset - entry.offset;
1898 let compression_method = range.ewf1_compression_method.ok_or_else(|| {
1899 EwfError::Malformed("EWF1 table range has no compression method".into())
1900 })?;
1901 let encoding = ewf1_chunk_encoding(
1902 entry.compressed,
1903 encoded_size,
1904 self.inner.info.chunk_size,
1905 compression_method,
1906 )?;
1907 validate_ewf1_encoded_size(
1908 encoded_size,
1909 self.inner.info.chunk_size,
1910 encoding,
1911 range.ewf1_allow_large_compressed_chunks,
1912 )?;
1913 Ok(Chunk {
1914 segment_index: range.segment_index,
1915 offset: entry.offset,
1916 encoded_size,
1917 logical_size,
1918 encoding,
1919 validate_checksum: encoding == ChunkEncoding::Raw
1920 && u64::try_from(logical_size)
1921 .ok()
1922 .and_then(|size| size.checked_add(4))
1923 == Some(encoded_size),
1924 })
1925 }
1926
1927 fn lookup_ewf2_chunk(
1928 &self,
1929 range: &TableRange,
1930 local_index: u64,
1931 logical_size: usize,
1932 ) -> Result<Chunk> {
1933 let entry_offset = table_entry_offset(range, local_index, ewf2::TABLE_ENTRY_SIZE as u64)?;
1934 let entry_data = self.read_table_bytes_at(
1935 range.segment_index,
1936 entry_offset,
1937 ewf2::TABLE_ENTRY_SIZE as u64,
1938 )?;
1939 let compression_method = range
1940 .ewf2_compression_method
1941 .map(ewf2::CompressionMethod::from)
1942 .ok_or_else(|| EwfError::Malformed("EWF2 range has no compression method".into()))?;
1943 let entry = ewf2::TableEntry::parse(&entry_data, compression_method)?;
1944 let encoding = match entry.kind {
1945 ewf2::ChunkKind::Raw | ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::None) => {
1946 ChunkEncoding::Raw
1947 }
1948 ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::Zlib) => ChunkEncoding::Zlib,
1949 ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::Bzip2) => ChunkEncoding::Bzip2,
1950 ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::Unknown(method)) => {
1951 return Err(EwfError::Unsupported(format!(
1952 "unknown EWF2 compression method {method}"
1953 )));
1954 }
1955 ewf2::ChunkKind::PatternFill => ChunkEncoding::PatternFill(entry.chunk_data_offset),
1956 };
1957 let validate_checksum = matches!(encoding, ChunkEncoding::Raw)
1958 && entry.flags & ewf2::CHUNK_FLAG_HAS_CHECKSUM != 0;
1959 validate_encoded_size(
1960 u64::from(entry.chunk_data_size),
1961 self.inner.info.chunk_size,
1962 encoding,
1963 )?;
1964 Ok(Chunk {
1965 segment_index: range.segment_index,
1966 offset: entry.chunk_data_offset,
1967 encoded_size: u64::from(entry.chunk_data_size),
1968 logical_size,
1969 encoding,
1970 validate_checksum,
1971 })
1972 }
1973
1974 fn read_u32_at(&self, segment_index: usize, offset: u64) -> Result<u32> {
1975 let data = self.read_table_bytes_at(segment_index, offset, 4)?;
1976 Ok(u32::from_le_bytes(
1977 data[..4].try_into().expect("slice length checked"),
1978 ))
1979 }
1980
1981 fn read_table_bytes_at(&self, segment_index: usize, offset: u64, size: u64) -> Result<Vec<u8>> {
1982 let requested = usize::try_from(size)
1983 .map_err(|_| EwfError::Malformed("table read size does not fit usize".into()))?;
1984 let cache_disabled = self
1985 .inner
1986 .table_page_cache
1987 .lock()
1988 .map_err(|_| EwfError::Malformed("table page cache lock poisoned".into()))?
1989 .is_disabled();
1990 if cache_disabled {
1991 self.inner.statistics.record_table_page_cache_access(false);
1992 return self.read_segment_range(segment_index, offset, size, false);
1993 }
1994 let mut output = Vec::with_capacity(requested);
1995 let mut current = offset;
1996 while output.len() < requested {
1997 let page_offset = current - current % TABLE_PAGE_SIZE;
1998 let key = TablePageKey {
1999 segment_index,
2000 page_offset,
2001 };
2002 let cached = self
2003 .inner
2004 .table_page_cache
2005 .lock()
2006 .map_err(|_| EwfError::Malformed("table page cache lock poisoned".into()))?
2007 .get(&key);
2008 self.inner
2009 .statistics
2010 .record_table_page_cache_access(cached.is_some());
2011 let page = if let Some(page) = cached {
2012 page
2013 } else {
2014 let bytes =
2015 self.read_segment_range(segment_index, page_offset, TABLE_PAGE_SIZE, true)?;
2016 self.inner
2017 .table_page_cache
2018 .lock()
2019 .map_err(|_| EwfError::Malformed("table page cache lock poisoned".into()))?
2020 .insert(key, bytes)
2021 };
2022 let within_page = usize::try_from(current - page_offset)
2023 .map_err(|_| EwfError::Malformed("table page offset does not fit usize".into()))?;
2024 let available = page.len().saturating_sub(within_page);
2025 if available == 0 {
2026 return Err(EwfError::Malformed("table read crosses segment end".into()));
2027 }
2028 let take = available.min(requested - output.len());
2029 output.extend_from_slice(&page[within_page..within_page + take]);
2030 current = current
2031 .checked_add(u64::try_from(take).expect("usize fits u64"))
2032 .ok_or_else(|| EwfError::Malformed("table read offset overflow".into()))?;
2033 }
2034 Ok(output)
2035 }
2036
2037 pub(crate) fn ensure_not_aborted(&self) -> Result<()> {
2038 if self.inner.abort_signaled.load(Ordering::Relaxed) {
2039 return Err(EwfError::Aborted);
2040 }
2041 Ok(())
2042 }
2043
2044 pub(crate) fn read_segment_range(
2045 &self,
2046 segment_index: usize,
2047 offset: u64,
2048 size: u64,
2049 clamp: bool,
2050 ) -> Result<Vec<u8>> {
2051 let bounded_size = |length: u64| -> Result<u64> {
2052 if clamp {
2053 let size = size.min(length.saturating_sub(offset));
2054 if size == 0 {
2055 return Err(EwfError::Malformed(
2056 "table page starts beyond segment end".into(),
2057 ));
2058 }
2059 return Ok(size);
2060 }
2061 if offset.checked_add(size).is_none_or(|end| end > length) {
2062 return Err(EwfError::Malformed(
2063 "requested byte range exceeds segment size".into(),
2064 ));
2065 }
2066 Ok(size)
2067 };
2068 if let Some(sources) = &self.inner.positioned_sources {
2069 let source = sources
2070 .get(segment_index)
2071 .ok_or_else(|| EwfError::Malformed("missing segment source".into()))?;
2072 let size = bounded_size(source.len())?;
2073 let mut bytes = vec![
2074 0;
2075 usize::try_from(size).map_err(|_| EwfError::Malformed(
2076 "segment read size exceeds usize".into()
2077 ))?
2078 ];
2079 source.read_exact_at(&mut bytes, offset)?;
2080 return Ok(bytes);
2081 }
2082 let path = self
2083 .inner
2084 .info
2085 .segment_paths
2086 .get(segment_index)
2087 .ok_or_else(|| EwfError::Malformed("missing segment source".into()))?;
2088
2089 #[cfg(any(unix, windows))]
2090 if !self.has_supplied_segment_readers()? {
2091 let wait_started = Instant::now();
2092 let (file, length) = {
2093 let mut segments =
2094 self.inner.segments.lock().map_err(|_| {
2095 EwfError::Malformed("segment file pool lock poisoned".into())
2096 })?;
2097 loop {
2098 if let Some(file) = segments.acquire_path_file(segment_index, path)? {
2099 let length = file.metadata()?.len();
2100 break (file, length);
2101 }
2102 segments =
2103 self.inner
2104 .segment_handle_available
2105 .wait(segments)
2106 .map_err(|_| {
2107 EwfError::Malformed("segment file pool lock poisoned".into())
2108 })?;
2109 }
2110 };
2111 self.inner
2112 .statistics
2113 .record_segment_pool_wait(wait_started.elapsed());
2114 let lease = PositionedSegmentLease {
2115 file: Some(file),
2116 segments: &self.inner.segments,
2117 available: &self.inner.segment_handle_available,
2118 };
2119 let size = bounded_size(length)?;
2120 let mut bytes = vec![
2121 0;
2122 usize::try_from(size).map_err(|_| EwfError::Malformed(
2123 "segment read size exceeds usize".into()
2124 ))?
2125 ];
2126 let read_started = Instant::now();
2127 read_file_exact_at(lease.file(), &mut bytes, offset)?;
2128 self.inner
2129 .statistics
2130 .record_segment_read(bytes.len(), read_started.elapsed());
2131 return Ok(bytes);
2132 }
2133
2134 let mut segments = self
2135 .inner
2136 .segments
2137 .lock()
2138 .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?;
2139 let size = bounded_size(segments.segment_len(segment_index, path)?)?;
2140 read_exact_at(
2141 segments.file_mut(segment_index, path)?.as_reader_mut(),
2142 offset,
2143 size,
2144 )
2145 }
2146
2147 fn has_supplied_segment_readers(&self) -> Result<bool> {
2148 Ok(self
2149 .inner
2150 .segments
2151 .lock()
2152 .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
2153 .has_supplied_readers())
2154 }
2155
2156 fn read_encoded_chunk_bytes(&self, chunk_id: u64, chunk: Chunk) -> Result<Vec<u8>> {
2157 self.ensure_not_aborted()?;
2158 if matches!(chunk.encoding, ChunkEncoding::PatternFill(_)) {
2159 return Ok(Vec::new());
2160 }
2161
2162 let mut encoded =
2163 self.read_segment_range(chunk.segment_index, chunk.offset, chunk.encoded_size, false)?;
2164 self.inner
2165 .statistics
2166 .record_encoded_bytes_read(chunk.encoded_size);
2167 if let Some(context) = self
2168 .inner
2169 .encryption_contexts
2170 .get(chunk.segment_index)
2171 .and_then(Option::as_ref)
2172 {
2173 let stream_offset = chunk_id
2174 .checked_mul(self.inner.info.chunk_size)
2175 .ok_or_else(|| EwfError::Malformed("encrypted chunk offset overflow".into()))?;
2176 context.apply_keystream(stream_offset, &mut encoded)?;
2177 }
2178 Ok(encoded)
2179 }
2180
2181 fn decode_chunk(&self, chunk_id: u64, chunk: Chunk) -> Result<Vec<u8>> {
2182 let encoded = self.read_encoded_chunk_bytes(chunk_id, chunk)?;
2183 if chunk.validate_checksum {
2184 validate_raw_chunk_checksum(&encoded, chunk.logical_size)?;
2185 }
2186 let decompression_started = (self.inner.statistics.enabled()
2187 && matches!(
2188 chunk.encoding,
2189 ChunkEncoding::Zlib | ChunkEncoding::Zstd | ChunkEncoding::Bzip2
2190 ))
2191 .then(Instant::now);
2192 let maximum_decoded_size = if self.inner.info.format == Format::Ewf1
2193 && matches!(
2194 self.inner.info.format_profile,
2195 FormatProfile::LogicalEnCase5
2196 | FormatProfile::LogicalEnCase6
2197 | FormatProfile::LogicalEnCase7
2198 ) {
2199 usize::try_from(self.inner.info.chunk_size)
2200 .map_err(|_| EwfError::Malformed("chunk size does not fit usize".into()))?
2201 } else {
2202 chunk.logical_size
2203 };
2204 let decoded = decode_chunk_with_padding(
2205 &encoded,
2206 chunk.encoding,
2207 chunk.logical_size,
2208 maximum_decoded_size,
2209 )?;
2210 if let Some(started) = decompression_started {
2211 self.inner
2212 .statistics
2213 .record_decompression(started.elapsed());
2214 }
2215 self.inner.statistics.record_decoded_bytes(decoded.len());
2216 Ok(decoded)
2217 }
2218
2219 pub(crate) fn verification_chunk(&self, chunk_id: u64) -> Result<Vec<u8>> {
2220 self.ensure_not_aborted()?;
2221 let chunk = self.lookup_chunk(chunk_id)?;
2222 self.decode_chunk(chunk_id, chunk).map_err(|error| {
2224 if matches!(error, EwfError::Malformed(_)) && self.encryption_info().is_some() {
2225 EwfError::DecryptionValidationFailed
2226 } else {
2227 error
2228 }
2229 })
2230 }
2231
2232 fn decode_chunk_with_policy(&self, chunk_id: u64, chunk: Chunk) -> Result<(Vec<u8>, bool)> {
2233 match self.decode_chunk(chunk_id, chunk) {
2234 Ok(decoded) => Ok((decoded, false)),
2235 Err(EwfError::Malformed(_))
2236 if self
2237 .inner
2238 .encryption_contexts
2239 .get(chunk.segment_index)
2240 .is_some_and(Option::is_some) =>
2241 {
2242 Err(EwfError::DecryptionValidationFailed)
2243 }
2244 Err(EwfError::Malformed(_)) if self.read_zero_chunk_on_error() => {
2245 self.record_checksum_error(chunk_id, chunk.logical_size)?;
2246 Ok((vec![0; chunk.logical_size], true))
2247 }
2248 Err(err) => Err(err),
2249 }
2250 }
2251
2252 fn record_checksum_error(&self, chunk_id: u64, logical_size: usize) -> Result<()> {
2253 let range = checksum_error_range(&self.inner.info, chunk_id, logical_size)?;
2254 let mut errors = self
2255 .inner
2256 .checksum_errors
2257 .lock()
2258 .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?;
2259 if !errors.contains(&range) {
2260 errors.push(range);
2261 }
2262 Ok(())
2263 }
2264}
2265
2266impl SegmentFilePool {
2267 fn new_path(
2268 segment_count: usize,
2269 maximum_open_handles: Option<usize>,
2270 statistics: Arc<ReaderStatisticsCollector>,
2271 ) -> Result<Self> {
2272 validate_maximum_open_handles(maximum_open_handles)?;
2273 Ok(Self {
2274 files: (0..segment_count).map(|_| None).collect(),
2275 lengths: vec![None; segment_count],
2276 ever_opened: vec![false; segment_count],
2277 open_order: VecDeque::new(),
2278 maximum_open_handles,
2279 mode: SegmentFilePoolMode::ReopenFromPath,
2280 statistics,
2281 })
2282 }
2283
2284 fn new_readers(
2285 readers: Vec<SuppliedSegmentReader>,
2286 maximum_open_handles: Option<usize>,
2287 statistics: Arc<ReaderStatisticsCollector>,
2288 ) -> Result<Self> {
2289 validate_maximum_open_handles(maximum_open_handles)?;
2290 let segment_count = readers.len();
2291 if maximum_open_handles.is_some_and(|maximum| maximum < segment_count) {
2292 return Err(EwfError::Unsupported(
2293 "maximum open handles cannot evict supplied segment readers".into(),
2294 ));
2295 }
2296
2297 statistics.record_segment_handle_open(segment_count);
2298 Ok(Self {
2299 files: readers
2300 .into_iter()
2301 .map(|reader| Some(SegmentFileHandle::Supplied(reader)))
2302 .collect(),
2303 lengths: vec![None; segment_count],
2304 ever_opened: vec![true; segment_count],
2305 open_order: (0..segment_count).collect(),
2306 maximum_open_handles,
2307 mode: SegmentFilePoolMode::SuppliedReaders,
2308 statistics,
2309 })
2310 }
2311
2312 fn maximum_open_handles(&self) -> Option<usize> {
2313 self.maximum_open_handles
2314 }
2315
2316 fn segment_len(&mut self, segment_index: usize, path: &Path) -> Result<u64> {
2317 if let Some(length) = self
2318 .lengths
2319 .get(segment_index)
2320 .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?
2321 {
2322 return Ok(*length);
2323 }
2324
2325 let length = self
2326 .file_mut(segment_index, path)?
2327 .as_reader_mut()
2328 .segment_len()?;
2329 let cached = self
2330 .lengths
2331 .get_mut(segment_index)
2332 .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
2333 *cached = Some(length);
2334 Ok(length)
2335 }
2336
2337 fn set_maximum_open_handles(&mut self, maximum_open_handles: Option<usize>) -> Result<()> {
2338 validate_maximum_open_handles(maximum_open_handles)?;
2339 if !self.can_close_handles()
2340 && maximum_open_handles.is_some_and(|maximum| maximum < self.open_count())
2341 {
2342 return Err(EwfError::Unsupported(
2343 "maximum open handles cannot evict supplied segment readers".into(),
2344 ));
2345 }
2346 let previous_maximum_open_handles = self.maximum_open_handles;
2347 self.maximum_open_handles = maximum_open_handles;
2348 if let Err(err) = self.enforce_limit() {
2349 self.maximum_open_handles = previous_maximum_open_handles;
2350 return Err(err);
2351 }
2352 Ok(())
2353 }
2354
2355 fn open_count(&self) -> usize {
2356 self.files.iter().filter(|file| file.is_some()).count()
2357 }
2358
2359 fn reserve_handle(&mut self) -> Result<()> {
2360 if let Some(maximum_open_handles) = self.maximum_open_handles {
2361 if self.open_count() >= maximum_open_handles && !self.can_close_handles() {
2362 return Err(EwfError::Unsupported(
2363 "maximum open handles cannot evict supplied segment readers".into(),
2364 ));
2365 }
2366 while self.open_count() >= maximum_open_handles {
2367 if !self.close_least_recently_used()? {
2368 return Err(EwfError::Unsupported(
2369 "maximum open handles are all serving active reads".into(),
2370 ));
2371 }
2372 }
2373 }
2374 Ok(())
2375 }
2376
2377 fn file_mut(&mut self, segment_index: usize, path: &Path) -> Result<&mut SegmentFileHandle> {
2378 let slot = self
2379 .files
2380 .get(segment_index)
2381 .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
2382 if slot.is_none() {
2383 self.reserve_handle()?;
2384 let was_opened = *self
2385 .ever_opened
2386 .get(segment_index)
2387 .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
2388 let file = match self.mode {
2389 SegmentFilePoolMode::ReopenFromPath => {
2390 Some(SegmentFileHandle::Path(PathSegmentReader {
2391 file: Arc::new(File::open(path)?),
2392 position: 0,
2393 }))
2394 }
2395 SegmentFilePoolMode::SuppliedReaders => None,
2396 }
2397 .ok_or_else(|| {
2398 EwfError::Malformed("supplied segment reader was unexpectedly closed".into())
2399 })?;
2400 let slot = self
2401 .files
2402 .get_mut(segment_index)
2403 .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
2404 *slot = Some(file);
2405 self.ever_opened[segment_index] = true;
2406 if was_opened {
2407 self.statistics.record_segment_handle_reopen();
2408 } else {
2409 self.statistics.record_segment_handle_open(1);
2410 }
2411 }
2412 self.mark_used(segment_index);
2413 self.files[segment_index]
2414 .as_mut()
2415 .ok_or_else(|| EwfError::Malformed("segment file was not opened".into()))
2416 }
2417
2418 fn acquire_path_file(
2419 &mut self,
2420 segment_index: usize,
2421 path: &Path,
2422 ) -> Result<Option<Arc<File>>> {
2423 if self.mode != SegmentFilePoolMode::ReopenFromPath {
2424 return Err(EwfError::Malformed(
2425 "positioned file requested for a supplied segment reader".into(),
2426 ));
2427 }
2428 let slot = self
2429 .files
2430 .get(segment_index)
2431 .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
2432 if slot.is_none() {
2433 if let Some(maximum_open_handles) = self.maximum_open_handles {
2434 while self.open_count() >= maximum_open_handles {
2435 if !self.close_least_recently_used()? {
2436 return Ok(None);
2437 }
2438 }
2439 }
2440 let was_opened = *self
2441 .ever_opened
2442 .get(segment_index)
2443 .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
2444 self.files[segment_index] = Some(SegmentFileHandle::Path(PathSegmentReader {
2445 file: Arc::new(File::open(path)?),
2446 position: 0,
2447 }));
2448 self.ever_opened[segment_index] = true;
2449 if was_opened {
2450 self.statistics.record_segment_handle_reopen();
2451 } else {
2452 self.statistics.record_segment_handle_open(1);
2453 }
2454 }
2455 self.mark_used(segment_index);
2456 self.files[segment_index]
2457 .as_ref()
2458 .and_then(SegmentFileHandle::path_file)
2459 .map(Arc::clone)
2460 .map(Some)
2461 .ok_or_else(|| EwfError::Malformed("path segment file was not opened".into()))
2462 }
2463
2464 fn can_close_handles(&self) -> bool {
2465 self.mode == SegmentFilePoolMode::ReopenFromPath
2466 }
2467
2468 fn has_supplied_readers(&self) -> bool {
2469 self.mode == SegmentFilePoolMode::SuppliedReaders
2470 }
2471
2472 fn mark_used(&mut self, segment_index: usize) {
2473 if let Some(position) = self
2474 .open_order
2475 .iter()
2476 .position(|open_segment_index| *open_segment_index == segment_index)
2477 {
2478 self.open_order.remove(position);
2479 }
2480 self.open_order.push_back(segment_index);
2481 }
2482
2483 fn enforce_limit(&mut self) -> Result<()> {
2484 if let Some(maximum_open_handles) = self.maximum_open_handles {
2485 if self.open_count() > maximum_open_handles && !self.can_close_handles() {
2486 return Err(EwfError::Unsupported(
2487 "maximum open handles cannot evict supplied segment readers".into(),
2488 ));
2489 }
2490 while self.open_count() > maximum_open_handles {
2491 if !self.close_least_recently_used()? {
2492 return Err(EwfError::Unsupported(
2493 "maximum open handles are all serving active reads".into(),
2494 ));
2495 }
2496 }
2497 }
2498 Ok(())
2499 }
2500
2501 fn close_least_recently_used(&mut self) -> Result<bool> {
2502 let candidates = self.open_order.len();
2503 for _ in 0..candidates {
2504 let Some(segment_index) = self.open_order.pop_front() else {
2505 break;
2506 };
2507 let Some(slot) = self.files.get_mut(segment_index) else {
2508 return Err(EwfError::Malformed("segment index out of range".into()));
2509 };
2510 if slot
2511 .as_ref()
2512 .is_some_and(SegmentFileHandle::idle_path_handle)
2513 {
2514 slot.take();
2515 return Ok(true);
2516 }
2517 if slot.is_some() {
2518 self.open_order.push_back(segment_index);
2519 }
2520 }
2521 Ok(false)
2522 }
2523}
2524
2525fn validate_maximum_open_handles(maximum_open_handles: Option<usize>) -> Result<()> {
2526 if maximum_open_handles == Some(0) {
2527 return Err(EwfError::Unsupported(
2528 "maximum open handles must be at least one".into(),
2529 ));
2530 }
2531 Ok(())
2532}
2533
2534fn data_chunk_encoding(encoding: ChunkEncoding) -> DataChunkEncoding {
2535 match encoding {
2536 ChunkEncoding::Raw => DataChunkEncoding::Raw,
2537 ChunkEncoding::Zlib => DataChunkEncoding::Zlib,
2538 ChunkEncoding::Zstd => DataChunkEncoding::Zstd,
2539 ChunkEncoding::Bzip2 => DataChunkEncoding::Bzip2,
2540 ChunkEncoding::PatternFill(pattern) => DataChunkEncoding::PatternFill(pattern),
2541 }
2542}
2543
2544impl ImageCursor {
2545 pub fn position(&self) -> u64 {
2547 self.position
2548 }
2549
2550 pub fn offset(&self) -> u64 {
2552 self.position()
2553 }
2554
2555 pub fn read_buffer(&mut self, buf: &mut [u8]) -> Result<usize> {
2562 let read = self.image.read_at(buf, self.position)?;
2563 self.position = self
2564 .position
2565 .checked_add(u64::try_from(read).expect("usize fits u64"))
2566 .ok_or_else(|| EwfError::Malformed("cursor position overflow".into()))?;
2567 Ok(read)
2568 }
2569
2570 pub fn read_buffer_at_offset(&mut self, buf: &mut [u8], offset: u64) -> Result<usize> {
2576 self.position = offset;
2577 self.read_buffer(buf)
2578 }
2579
2580 pub fn seek_offset(&mut self, pos: SeekFrom) -> Result<u64> {
2587 self.seek_position(pos).map_err(EwfError::from)
2588 }
2589
2590 pub fn segment_filename(&self) -> Result<Option<&Path>> {
2598 self.image.segment_filename_for_offset(self.position)
2599 }
2600
2601 pub fn read_data_chunk(&mut self) -> Result<Option<DataChunk>> {
2610 let Some(chunk_index) = self.current_chunk_index()? else {
2611 return Ok(None);
2612 };
2613 let chunk = self.image.read_data_chunk(chunk_index)?;
2614 self.advance_to_chunk_end(chunk.logical_offset, chunk.logical_size)?;
2615 Ok(Some(chunk))
2616 }
2617
2618 pub fn read_encoded_data_chunk(&mut self) -> Result<Option<EncodedDataChunk>> {
2627 let Some(chunk_index) = self.current_chunk_index()? else {
2628 return Ok(None);
2629 };
2630 let chunk = self.image.read_encoded_data_chunk(chunk_index)?;
2631 self.advance_to_chunk_end(chunk.logical_offset, chunk.logical_size)?;
2632 Ok(Some(chunk))
2633 }
2634
2635 fn current_chunk_index(&self) -> Result<Option<u64>> {
2636 if self.position >= self.image.info().logical_size {
2637 return Ok(None);
2638 }
2639 let chunk_size = self.image.info().chunk_size;
2640 if chunk_size == 0 {
2641 return Err(EwfError::Malformed("chunk size is zero".into()));
2642 }
2643 Ok(Some(self.position / chunk_size))
2644 }
2645
2646 fn advance_to_chunk_end(&mut self, logical_offset: u64, logical_size: usize) -> Result<()> {
2647 let logical_size = u64::try_from(logical_size)
2648 .map_err(|_| EwfError::Malformed("chunk logical size does not fit u64".into()))?;
2649 self.position = logical_offset
2650 .checked_add(logical_size)
2651 .ok_or_else(|| EwfError::Malformed("cursor chunk end offset overflow".into()))?;
2652 Ok(())
2653 }
2654
2655 fn seek_position(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
2656 let next = match pos {
2657 SeekFrom::Start(offset) => i128::from(offset),
2658 SeekFrom::End(offset) => {
2659 i128::from(self.image.info().logical_size) + i128::from(offset)
2660 }
2661 SeekFrom::Current(offset) => i128::from(self.position) + i128::from(offset),
2662 };
2663 if next < 0 {
2664 return Err(std::io::Error::new(
2665 std::io::ErrorKind::InvalidInput,
2666 "seek before start of image",
2667 ));
2668 }
2669 self.position = u64::try_from(next).map_err(|_| {
2670 std::io::Error::new(
2671 std::io::ErrorKind::InvalidInput,
2672 "seek position does not fit u64",
2673 )
2674 })?;
2675 Ok(self.position)
2676 }
2677}
2678
2679impl Read for ImageCursor {
2680 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2681 self.read_buffer(buf).map_err(std::io::Error::other)
2682 }
2683}
2684
2685impl Seek for ImageCursor {
2686 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
2687 self.seek_position(pos)
2688 }
2689}
2690
2691impl SingleFileCursor {
2692 pub fn position(&self) -> u64 {
2694 self.position
2695 }
2696
2697 pub fn offset(&self) -> u64 {
2699 self.position()
2700 }
2701
2702 pub fn read_buffer(&mut self, buf: &mut [u8]) -> Result<usize> {
2709 let read = self
2710 .image
2711 .read_single_file_at(&self.entry, buf, self.position)?;
2712 self.position = self
2713 .position
2714 .checked_add(u64::try_from(read).expect("usize fits u64"))
2715 .ok_or_else(|| EwfError::Malformed("single file cursor position overflow".into()))?;
2716 Ok(read)
2717 }
2718
2719 pub fn read_buffer_at_offset(&mut self, buf: &mut [u8], offset: u64) -> Result<usize> {
2725 self.position = offset;
2726 self.read_buffer(buf)
2727 }
2728
2729 pub fn seek_offset(&mut self, pos: SeekFrom) -> Result<u64> {
2736 self.seek_position(pos).map_err(EwfError::from)
2737 }
2738
2739 fn seek_position(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
2740 let file_size = single_file_size(&self.entry).map_err(std::io::Error::other)?;
2741 let next = match pos {
2742 SeekFrom::Start(offset) => i128::from(offset),
2743 SeekFrom::End(offset) => i128::from(file_size) + i128::from(offset),
2744 SeekFrom::Current(offset) => i128::from(self.position) + i128::from(offset),
2745 };
2746 if next < 0 {
2747 return Err(std::io::Error::new(
2748 std::io::ErrorKind::InvalidInput,
2749 "cannot seek before start of single file",
2750 ));
2751 }
2752 self.position = u64::try_from(next).map_err(|_| {
2753 std::io::Error::new(
2754 std::io::ErrorKind::InvalidInput,
2755 "single file cursor position does not fit u64",
2756 )
2757 })?;
2758 Ok(self.position)
2759 }
2760}
2761
2762impl Read for SingleFileCursor {
2763 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2764 self.read_buffer(buf).map_err(std::io::Error::other)
2765 }
2766}
2767
2768impl Seek for SingleFileCursor {
2769 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
2770 self.seek_position(pos)
2771 }
2772
2773 fn stream_position(&mut self) -> std::io::Result<u64> {
2774 Ok(self.position)
2775 }
2776}
2777
2778pub(crate) fn single_file_size(entry: &SingleFileEntry) -> Result<u64> {
2779 if let Some(size) = entry.size {
2780 return Ok(size);
2781 }
2782
2783 entry.extents.iter().try_fold(0_u64, |total, extent| {
2784 total
2785 .checked_add(extent.data_size)
2786 .ok_or_else(|| EwfError::Malformed("single file size overflow".into()))
2787 })
2788}
2789
2790struct ParsedSegment {
2791 format: Format,
2792 sections: Vec<SectionInfo>,
2793 format_profile: FormatProfile,
2794 format_profile_hint_only: bool,
2795 segment_number: u64,
2796 set_identifier: Option<[u8; 16]>,
2797 ewf2_header_profile: Option<Ewf2HeaderProfile>,
2798 ewf1_compression_method: Option<CompressionMethod>,
2799 ewf1_media_geometry: Option<Ewf1MediaGeometry>,
2800 chunk_size: u64,
2801 logical_size: u64,
2802 acquisition_complete: bool,
2803 media: MediaInfo,
2804 ranges: Vec<TableRange>,
2805 table_chunk_count: u64,
2806 metadata: EwfMetadata,
2807 stored_hashes: StoredHashes,
2808 acquisition_errors: Vec<AcquisitionError>,
2809 memory_extents: Vec<MemoryExtent>,
2810 single_files: Option<SingleFilesInfo>,
2811 ewf2_single_files_tables: SingleFilesAuxTables,
2812 ewf2_increment_data: Vec<Vec<u8>>,
2813 ewf2_final_information: Option<Vec<u8>>,
2814 ewf2_restart_data: Option<String>,
2815 ewf2_analytical_data: Option<String>,
2816 sessions: Vec<SectorRange>,
2817 tracks: Vec<SectorRange>,
2818 ewf2_device_information: Option<Vec<u8>>,
2819 ewf2_case_data: Option<Vec<u8>>,
2820}
2821
2822#[derive(Clone, Copy)]
2823struct Ewf2HeaderProfile {
2824 major_version: u8,
2825 minor_version: u8,
2826 compression_method: ewf2::CompressionMethod,
2827}
2828
2829#[derive(Clone, Copy, PartialEq, Eq)]
2830struct Ewf1MediaGeometry {
2831 chunk_size: u64,
2832 logical_size: u64,
2833 sectors_per_chunk: u32,
2834 bytes_per_sector: u32,
2835 sector_count: u64,
2836 chunk_count: u32,
2837}
2838
2839#[derive(Clone, Copy)]
2840struct Ewf1SegmentContext {
2841 first_chunk: u64,
2842 inherited_media_geometry: Option<Ewf1MediaGeometry>,
2843}
2844
2845#[derive(Clone, Copy)]
2846struct Ewf1RangeProfile {
2847 allow_large_compressed_chunks: bool,
2848 compression_method: CompressionMethod,
2849}
2850
2851#[derive(Clone)]
2852struct Section {
2853 desc: ewf1::SectionDescriptor,
2854 data_offset: u64,
2855 data_size: u64,
2856}
2857
2858#[derive(Clone, Copy)]
2859struct Ewf2Section {
2860 desc: ewf2::SectionDescriptor,
2861 data_offset: u64,
2862 data_size: u64,
2863 layout: Ewf2SectionLayout,
2864}
2865
2866#[derive(Clone, Copy, PartialEq, Eq)]
2867enum Ewf2SectionLayout {
2868 LeadingDescriptor,
2869 TrailingDescriptor,
2870}
2871
2872fn parse_ewf1_segment(
2873 file: &mut dyn SegmentReader,
2874 segment_index: usize,
2875 first_chunk: u64,
2876 inherited_media_geometry: Option<Ewf1MediaGeometry>,
2877 profile_hint: FormatProfile,
2878 header_codepage: HeaderCodepage,
2879 statistics: &ReaderStatisticsCollector,
2880) -> Result<ParsedSegment> {
2881 let mut header = [0; ewf1::FILE_HEADER_SIZE];
2882 file.seek(SeekFrom::Start(0))?;
2883 file.read_exact(&mut header)?;
2884 let file_header = ewf1::FileHeader::parse(&header)?;
2885
2886 let sections = scan_ewf1_sections(file)?;
2887 let compression_method = detect_ewf1_compression_method(file_header, §ions)?;
2888 let acquisition_complete = ewf1_acquisition_complete(§ions);
2889 let volume = sections
2890 .iter()
2891 .find(|section| {
2892 matches!(
2893 section.desc.section_type.as_str(),
2894 "volume" | "disk" | "data"
2895 )
2896 })
2897 .map(|section| {
2898 let data = read_exact_at(file, section.data_offset, section.data_size)?;
2899 validate_present_ewf1_media_checksum(&data, §ion.desc.section_type)?;
2900 ewf1::Volume::parse(&data)
2901 })
2902 .transpose()?;
2903 if segment_index == 0 && volume.is_none() {
2904 return Err(EwfError::Malformed("missing EWF1 media section".into()));
2905 }
2906 let (chunk_size, logical_size, smart_profile, media) = if let Some(volume) = volume.as_ref() {
2907 let chunk_size = volume.chunk_size()?;
2908 validate_chunk_size(chunk_size)?;
2909 let declared_logical_size = volume.logical_size()?;
2910 let logical_size = if declared_logical_size > 0 {
2911 declared_logical_size
2912 } else {
2913 chunk_size
2914 .checked_mul(u64::from(volume.chunk_count))
2915 .ok_or_else(|| EwfError::Malformed("EWF1 logical size overflow".into()))?
2916 };
2917 let smart_profile = !file_header.logical && volume.smart;
2918 let media = MediaInfo {
2919 sectors_per_chunk: Some(u64::from(volume.sectors_per_chunk)),
2920 bytes_per_sector: Some(u64::from(volume.bytes_per_sector)),
2921 sector_count: Some(volume.sector_count),
2922 chunk_count: Some(u64::from(volume.chunk_count)),
2923 error_granularity: volume.error_granularity.map(u64::from),
2924 set_identifier: volume.set_identifier,
2925 ewf2_segment_file_version: None,
2926 compression_method: Some(compression_method),
2927 compression_values: CompressionValues {
2928 level: volume
2929 .compression_level
2930 .map(|value| CompressionLevel::from_i8(value as i8))
2931 .unwrap_or_default(),
2932 ..CompressionValues::default()
2933 },
2934 media_type: volume
2935 .media_type
2936 .map(ewf1_media_type)
2937 .or_else(|| smart_profile.then_some(MediaType::Removable)),
2938 media_flags: ewf1_media_flags(volume.media_flags, file_header.logical || smart_profile),
2939 };
2940 (chunk_size, logical_size, smart_profile, media)
2941 } else {
2942 (0, 0, false, MediaInfo::default())
2943 };
2944 let ewf1_media_geometry = volume.as_ref().map(|volume| Ewf1MediaGeometry {
2945 chunk_size,
2946 logical_size,
2947 sectors_per_chunk: volume.sectors_per_chunk,
2948 bytes_per_sector: volume.bytes_per_sector,
2949 sector_count: volume.sector_count,
2950 chunk_count: volume.chunk_count,
2951 });
2952
2953 let mut metadata = EwfMetadata::default();
2954 let mut stored_hashes = StoredHashes::default();
2955 let mut acquisition_errors = Vec::new();
2956 let memory_extents = Vec::new();
2957 let mut single_files = None;
2958 let mut format_profile = if file_header.logical {
2959 FormatProfile::LogicalEnCase5
2960 } else if smart_profile {
2961 FormatProfile::Smart
2962 } else if volume.is_none() {
2963 FormatProfile::Unknown
2964 } else if profile_hint != FormatProfile::Unknown {
2965 profile_hint
2966 } else {
2967 FormatProfile::EnCase5
2968 };
2969 let mut format_profile_hint_only = !file_header.logical && !smart_profile;
2970 let mut format_profile_detected_from_header2 = false;
2971 let mut sessions = Vec::new();
2972 let mut tracks = Vec::new();
2973 for section in §ions {
2974 match section.desc.section_type.as_str() {
2975 "header" => {
2976 let data = read_exact_at(file, section.data_offset, section.data_size)?;
2977 let payload = ewf1_metadata_payload(&data, compression_method)?;
2978 let text = decode_header_bytes(&payload, header_codepage);
2979 if !format_profile_detected_from_header2
2980 && apply_detected_ewf1_format_profile(
2981 &mut format_profile,
2982 detect_ewf1_header_profile(&text, 1),
2983 )
2984 {
2985 format_profile_hint_only = false;
2986 }
2987 parse_header_data(&payload, header_codepage, &mut metadata);
2988 }
2989 "header2" => {
2990 let data = read_exact_at(file, section.data_offset, section.data_size)?;
2991 let payload = ewf1_metadata_payload(&data, compression_method)?;
2992 if apply_detected_ewf1_format_profile(
2993 &mut format_profile,
2994 detect_ewf1_header2_profile(&payload),
2995 ) {
2996 format_profile_hint_only = false;
2997 format_profile_detected_from_header2 = true;
2998 }
2999 parse_header2_data(&payload, &mut metadata);
3000 }
3001 "xheader" => {
3002 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3003 let payload = ewf1_metadata_payload(&data, compression_method)?;
3004 parse_xheader_data(&payload, &mut metadata);
3005 }
3006 "error2" => {
3007 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3008 acquisition_errors.extend(parse_error2_data(&data)?);
3009 }
3010 "session" => {
3011 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3012 let sector_count = volume
3013 .as_ref()
3014 .map(|volume| volume.sector_count)
3015 .or_else(|| inherited_media_geometry.map(|geometry| geometry.sector_count))
3016 .ok_or_else(|| {
3017 EwfError::Malformed(
3018 "EWF1 session section has no media geometry to inherit".into(),
3019 )
3020 })?;
3021 let parsed_sessions = parse_session_data(&data, 1, sector_count)?;
3022 sessions.extend(parsed_sessions.sessions);
3023 tracks.extend(parsed_sessions.tracks);
3024 }
3025 "hash" => {
3026 validate_hash_section_size(section.data_size, EWF1_HASH_SECTION_SIZE, "EWF1 MD5")?;
3027 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3028 validate_adler32_checksum(&data, 32, 32, "EWF1 MD5 hash")?;
3029 if let Some(hash) = parse_nonzero_hash::<16>(&data) {
3030 insert_hash_value(&mut stored_hashes, "MD5", &hash)?;
3031 }
3032 }
3033 "digest" => {
3034 validate_hash_section_size(
3035 section.data_size,
3036 EWF1_DIGEST_SECTION_SIZE,
3037 "EWF1 digest",
3038 )?;
3039 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3040 validate_adler32_checksum(&data, 76, 76, "EWF1 digest")?;
3041 if let Some(hash) = parse_nonzero_hash::<16>(&data[..16]) {
3042 insert_hash_value(&mut stored_hashes, "MD5", &hash)?;
3043 }
3044 if let Some(hash) = parse_nonzero_hash::<20>(&data[16..36]) {
3045 insert_hash_value(&mut stored_hashes, "SHA1", &hash)?;
3046 }
3047 }
3048 "xhash" => {
3049 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3050 let payload = ewf1_metadata_payload(&data, compression_method)?;
3051 parse_xhash_data(&payload, &mut stored_hashes)?;
3052 }
3053 "ltree" => {
3054 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3055 merge_single_files(&mut single_files, Some(parse_ewf1_ltree_data(&data)?))?;
3056 }
3057 _ => {}
3058 }
3059 }
3060
3061 let ranges = parse_ewf1_ranges(
3062 file,
3063 §ions,
3064 segment_index,
3065 first_chunk,
3066 logical_size,
3067 Ewf1RangeProfile {
3068 allow_large_compressed_chunks: volume.as_ref().is_some_and(|volume| volume.smart),
3069 compression_method,
3070 },
3071 statistics,
3072 )?;
3073 let table_chunk_count = ranges.iter().try_fold(0_u64, |count, range| {
3074 count
3075 .checked_add(range.chunk_count)
3076 .ok_or_else(|| EwfError::Malformed("EWF1 table chunk count overflow".into()))
3077 })?;
3078 Ok(ParsedSegment {
3079 format: Format::Ewf1,
3080 sections: sections
3081 .iter()
3082 .map(|section| SectionInfo {
3083 segment_index,
3084 kind: SectionKind::Ewf1(section.desc.section_type.clone()),
3085 descriptor_offset: section.desc.offset,
3086 data_offset: section.data_offset,
3087 data_size: section.data_size,
3088 linked_descriptor_offset: section.desc.next,
3089 data_flags: 0,
3090 })
3091 .collect(),
3092 format_profile,
3093 format_profile_hint_only,
3094 segment_number: u64::from(file_header.segment_number),
3095 set_identifier: volume.as_ref().and_then(|volume| volume.set_identifier),
3096 ewf2_header_profile: None,
3097 ewf1_compression_method: Some(compression_method),
3098 ewf1_media_geometry,
3099 chunk_size,
3100 logical_size,
3101 acquisition_complete,
3102 media,
3103 ranges,
3104 table_chunk_count,
3105 metadata,
3106 stored_hashes,
3107 acquisition_errors,
3108 memory_extents,
3109 single_files,
3110 ewf2_single_files_tables: SingleFilesAuxTables::default(),
3111 ewf2_increment_data: Vec::new(),
3112 ewf2_final_information: None,
3113 ewf2_restart_data: None,
3114 ewf2_analytical_data: None,
3115 sessions,
3116 tracks,
3117 ewf2_device_information: None,
3118 ewf2_case_data: None,
3119 })
3120}
3121
3122fn ewf1_media_type(value: u8) -> MediaType {
3123 match value {
3124 0x00 => MediaType::Removable,
3125 0x01 => MediaType::Fixed,
3126 0x03 => MediaType::Optical,
3127 0x0e => MediaType::SingleFiles,
3128 0x10 => MediaType::Memory,
3129 value => MediaType::Unknown(value),
3130 }
3131}
3132
3133fn ewf1_media_flags(value: Option<u8>, logical_file_header: bool) -> MediaFlags {
3134 value.map_or(
3135 MediaFlags {
3136 physical: !logical_file_header,
3137 fastbloc: false,
3138 tableau: false,
3139 },
3140 |value| MediaFlags {
3141 physical: value & 0x02 != 0,
3142 fastbloc: value & 0x04 != 0,
3143 tableau: value & 0x08 != 0,
3144 },
3145 )
3146}
3147
3148fn detect_ewf1_compression_method(
3149 file_header: ewf1::FileHeader,
3150 sections: &[Section],
3151) -> Result<CompressionMethod> {
3152 let header_method = match file_header.format_marker {
3153 0x01 | 0x81 => CompressionMethod::Zlib,
3154 0x21 | 0xa1 => CompressionMethod::Zstd,
3155 marker => {
3156 return Err(EwfError::Malformed(format!(
3157 "unsupported EWF1 file header marker 0x{marker:02x}"
3158 )));
3159 }
3160 };
3161
3162 let mut descriptor_method = None;
3163 for marker in sections
3164 .iter()
3165 .filter(|section| section.desc.section_type == "header")
3166 .filter_map(|section| section.desc.compression_method.as_deref())
3167 {
3168 let observed = if marker.eq_ignore_ascii_case("zstd") {
3169 CompressionMethod::Zstd
3170 } else if marker.eq_ignore_ascii_case("zlib") || marker.eq_ignore_ascii_case("deflate") {
3171 CompressionMethod::Zlib
3172 } else {
3173 return Err(EwfError::Malformed(format!(
3174 "unsupported EWF1 descriptor compression marker {marker:?}"
3175 )));
3176 };
3177 if descriptor_method.is_some_and(|expected| expected != observed) {
3178 return Err(EwfError::Malformed(
3179 "EWF1 descriptor compression marker conflict".into(),
3180 ));
3181 }
3182 descriptor_method.get_or_insert(observed);
3183 }
3184
3185 if descriptor_method.is_some_and(|observed| observed != header_method) {
3186 return Err(EwfError::Malformed(
3187 "EWF1 file header and descriptor compression marker conflict".into(),
3188 ));
3189 }
3190 Ok(header_method)
3191}
3192
3193fn ewf1_format_profile_hint_from_path(path: &Path) -> FormatProfile {
3194 match path.extension().and_then(|extension| extension.to_str()) {
3195 Some(extension) if extension.starts_with('e') => FormatProfile::Ewf,
3196 Some(extension) if extension.starts_with('E') => FormatProfile::EnCase2,
3197 Some(extension) if extension.starts_with('L') => FormatProfile::LogicalEnCase5,
3198 Some(extension) if extension.starts_with('s') || extension.starts_with('S') => {
3199 FormatProfile::Smart
3200 }
3201 _ => FormatProfile::Unknown,
3202 }
3203}
3204
3205fn parse_segment(
3206 file: &mut dyn SegmentReader,
3207 first_path: &Path,
3208 segment_index: usize,
3209 ewf1_context: Ewf1SegmentContext,
3210 strictness: OpenStrictness,
3211 header_codepage: HeaderCodepage,
3212 statistics: &ReaderStatisticsCollector,
3213) -> Result<ParsedSegment> {
3214 let mut signature = [0; 8];
3215 file.seek(SeekFrom::Start(0))?;
3216 file.read_exact(&mut signature)?;
3217 if signature == ewf1::EVF_SIGNATURE || signature == ewf1::LVF_SIGNATURE {
3218 parse_ewf1_segment(
3219 file,
3220 segment_index,
3221 ewf1_context.first_chunk,
3222 ewf1_context.inherited_media_geometry,
3223 ewf1_format_profile_hint_from_path(first_path),
3227 header_codepage,
3228 statistics,
3229 )
3230 } else if signature == ewf2::EX01_SIGNATURE || signature == ewf2::LEF2_SIGNATURE {
3231 parse_ewf2_segment(file, segment_index, strictness, statistics)
3232 } else {
3233 Err(EwfError::InvalidSignature)
3234 }
3235}
3236
3237fn parse_ewf2_segment(
3238 file: &mut dyn SegmentReader,
3239 segment_index: usize,
3240 strictness: OpenStrictness,
3241 statistics: &ReaderStatisticsCollector,
3242) -> Result<ParsedSegment> {
3243 let mut header = [0; ewf2::FILE_HEADER_SIZE];
3244 file.seek(SeekFrom::Start(0))?;
3245 file.read_exact(&mut header)?;
3246 let header = ewf2::FileHeader::parse(&header)?;
3247 let sections = scan_ewf2_sections(file, strictness)?;
3248 let acquisition_complete = ewf2_acquisition_complete(§ions);
3249
3250 let mut chunk_size = 0;
3251 let mut logical_size = 0;
3252 let mut metadata = EwfMetadata::default();
3253 let mut stored_hashes = StoredHashes::default();
3254 let mut acquisition_errors = Vec::new();
3255 let mut memory_extents = Vec::new();
3256 let mut single_files = None;
3257 let mut ewf2_single_files_tables = SingleFilesAuxTables::default();
3258 let mut ewf2_increment_data = Vec::new();
3259 let mut ewf2_final_information = None;
3260 let mut ewf2_restart_data = None;
3261 let mut ewf2_analytical_data = None;
3262 let mut session_table_data = Vec::new();
3263 let mut sessions = Vec::new();
3264 let mut tracks = Vec::new();
3265 let mut media = MediaInfo {
3266 sectors_per_chunk: None,
3267 bytes_per_sector: None,
3268 sector_count: None,
3269 chunk_count: None,
3270 error_granularity: None,
3271 set_identifier: Some(header.set_identifier),
3272 ewf2_segment_file_version: Some(SegmentFileVersion {
3273 major: header.major_version,
3274 minor: header.minor_version,
3275 }),
3276 compression_method: Some(public_compression_method(header.compression_method)),
3277 compression_values: CompressionValues::default(),
3278 media_type: None,
3279 media_flags: MediaFlags {
3280 physical: !header.logical,
3281 fastbloc: false,
3282 tableau: false,
3283 },
3284 };
3285 let mut device_information = None;
3286 let mut case_data = None;
3287 for section in §ions {
3288 match section.desc.section_type {
3289 ewf2::SectionType::DeviceInformation => {
3290 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3291 let data = ewf2_metadata_payload(&data, header.compression_method)?;
3292 remember_ewf2_metadata_payload(
3293 &mut device_information,
3294 &data,
3295 "device information",
3296 )?;
3297 parse_ewf2_device_info_values(&data, &mut metadata);
3298 let geometry = parse_ewf2_device_info(&data)?;
3299 if let Some(size) = geometry.chunk_size {
3300 chunk_size = size;
3301 }
3302 if let Some(size) = geometry.logical_size {
3303 logical_size = size;
3304 }
3305 apply_ewf2_geometry(&mut media, geometry);
3306 }
3307 ewf2::SectionType::CaseData => {
3308 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3309 let data = ewf2_metadata_payload(&data, header.compression_method)?;
3310 remember_ewf2_metadata_payload(&mut case_data, &data, "case data")?;
3311 parse_ewf2_case_data(&data, &mut metadata);
3312 let geometry = parse_ewf2_device_info(&data)?;
3313 if chunk_size == 0
3314 && let Some(size) = geometry.chunk_size
3315 {
3316 chunk_size = size;
3317 }
3318 if logical_size == 0
3319 && let Some(size) = geometry.logical_size
3320 {
3321 logical_size = size;
3322 }
3323 apply_ewf2_geometry_if_missing(&mut media, geometry);
3324 }
3325 ewf2::SectionType::Md5Hash => {
3326 validate_hash_section_size(section.data_size, EWF2_HASH_SECTION_SIZE, "EWF2 MD5")?;
3327 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3328 validate_adler32_checksum(&data, 16, 16, "EWF2 MD5 hash")?;
3329 if let Some(hash) = parse_nonzero_hash::<16>(&data) {
3330 insert_hash_value(&mut stored_hashes, "MD5", &hash)?;
3331 }
3332 }
3333 ewf2::SectionType::Sha1Hash => {
3334 validate_hash_section_size(section.data_size, EWF2_HASH_SECTION_SIZE, "EWF2 SHA1")?;
3335 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3336 validate_adler32_checksum(&data, 20, 20, "EWF2 SHA1 hash")?;
3337 if let Some(hash) = parse_nonzero_hash::<20>(&data) {
3338 insert_hash_value(&mut stored_hashes, "SHA1", &hash)?;
3339 }
3340 }
3341 ewf2::SectionType::ErrorTable => {
3342 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3343 acquisition_errors.extend(parse_ewf2_error_table_data(&data)?);
3344 }
3345 ewf2::SectionType::MemoryExtentsTable => {
3346 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3347 memory_extents.extend(parse_ewf2_memory_extents_table(&data)?);
3348 }
3349 ewf2::SectionType::SingleFilesData => {
3350 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3351 merge_single_files(
3352 &mut single_files,
3353 Some(parse_ewf2_single_files_data(&data)?),
3354 )?;
3355 }
3356 ewf2::SectionType::SingleFilesTable => {
3357 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3358 merge_single_files_aux_u64_table(
3359 &mut ewf2_single_files_tables.table_0x21_entries,
3360 parse_ewf2_single_files_aux_u64_table(&data, "EWF2 single files 0x21 table")?,
3361 "0x21",
3362 )?;
3363 }
3364 ewf2::SectionType::SingleFilesMd5HashTable => {
3365 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3366 merge_single_files_aux_md5_table(
3367 &mut ewf2_single_files_tables.md5_hashes,
3368 parse_ewf2_single_files_md5_hash_table(&data)?,
3369 )?;
3370 }
3371 ewf2::SectionType::SingleFilesUnknownTable => {
3372 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3373 merge_single_files_aux_u64_table(
3374 &mut ewf2_single_files_tables.table_0x23_entries,
3375 parse_ewf2_single_files_aux_u64_table(&data, "EWF2 single files 0x23 table")?,
3376 "0x23",
3377 )?;
3378 }
3379 ewf2::SectionType::IncrementData => {
3380 ewf2_increment_data.push(read_exact_at(
3381 file,
3382 section.data_offset,
3383 section.data_size,
3384 )?);
3385 }
3386 ewf2::SectionType::FinalInformation => {
3387 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3388 merge_optional_ewf2_raw_section(
3389 &mut ewf2_final_information,
3390 Some(data),
3391 "final information",
3392 )?;
3393 }
3394 ewf2::SectionType::RestartData => {
3395 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3396 ewf2_restart_data = Some(decode_ewf2_string_section(
3397 &data,
3398 header.compression_method,
3399 "restart data",
3400 )?);
3401 }
3402 ewf2::SectionType::AnalyticalData => {
3403 let data = read_exact_at(file, section.data_offset, section.data_size)?;
3404 ewf2_analytical_data = Some(decode_ewf2_string_section(
3405 &data,
3406 header.compression_method,
3407 "analytical data",
3408 )?);
3409 }
3410 ewf2::SectionType::SessionTable => {
3411 session_table_data.push(read_exact_at(
3412 file,
3413 section.data_offset,
3414 section.data_size,
3415 )?);
3416 }
3417 _ => {}
3418 }
3419 }
3420
3421 let media_sector_count = media.sector_count.unwrap_or(0);
3422 for data in session_table_data {
3423 let parsed_sessions = parse_session_data(&data, 2, media_sector_count)?;
3424 sessions.extend(parsed_sessions.sessions);
3425 tracks.extend(parsed_sessions.tracks);
3426 }
3427
3428 if chunk_size == 0 {
3429 chunk_size = 32_768;
3430 }
3431 validate_chunk_size(chunk_size)?;
3432 let ranges = parse_ewf2_ranges(
3433 file,
3434 §ions,
3435 segment_index,
3436 logical_size,
3437 header.compression_method,
3438 statistics,
3439 )?;
3440 if logical_size == 0 {
3441 let discovered_chunks = ranges.iter().try_fold(0_u64, |max, range| {
3442 let end = range
3443 .first_chunk
3444 .checked_add(range.chunk_count)
3445 .ok_or_else(|| EwfError::Malformed("EWF2 table chunk count overflow".into()))?;
3446 Ok::<u64, EwfError>(max.max(end))
3447 })?;
3448 logical_size = chunk_size
3449 .checked_mul(discovered_chunks)
3450 .ok_or_else(|| EwfError::Malformed("EWF2 logical size overflow".into()))?;
3451 }
3452 let table_chunk_count = ranges.iter().try_fold(0_u64, |count, range| {
3453 count
3454 .checked_add(range.chunk_count)
3455 .ok_or_else(|| EwfError::Malformed("EWF2 table chunk count overflow".into()))
3456 })?;
3457
3458 Ok(ParsedSegment {
3459 format: Format::Ewf2,
3460 sections: sections
3461 .iter()
3462 .map(|section| {
3463 let raw_kind = read_exact_at(file, section.desc.offset, 4)?;
3464 Ok(SectionInfo {
3465 segment_index,
3466 kind: SectionKind::Ewf2(u32::from_le_bytes(
3467 raw_kind[..4].try_into().expect("four bytes read"),
3468 )),
3469 descriptor_offset: section.desc.offset,
3470 data_offset: section.data_offset,
3471 data_size: section.data_size,
3472 linked_descriptor_offset: section.desc.previous_offset,
3473 data_flags: section.desc.data_flags,
3474 })
3475 })
3476 .collect::<Result<Vec<_>>>()?,
3477 format_profile: if header.logical {
3478 FormatProfile::Ewf2LogicalEnCase7
3479 } else {
3480 FormatProfile::Ewf2EnCase7
3481 },
3482 format_profile_hint_only: false,
3483 segment_number: u64::from(header.segment_number),
3484 set_identifier: Some(header.set_identifier),
3485 ewf2_header_profile: Some(Ewf2HeaderProfile {
3486 major_version: header.major_version,
3487 minor_version: header.minor_version,
3488 compression_method: header.compression_method,
3489 }),
3490 ewf1_compression_method: None,
3491 ewf1_media_geometry: None,
3492 chunk_size,
3493 logical_size,
3494 acquisition_complete,
3495 media,
3496 ranges,
3497 table_chunk_count,
3498 metadata,
3499 stored_hashes,
3500 acquisition_errors,
3501 memory_extents,
3502 single_files,
3503 ewf2_single_files_tables,
3504 ewf2_increment_data,
3505 ewf2_final_information,
3506 ewf2_restart_data,
3507 ewf2_analytical_data,
3508 sessions,
3509 tracks,
3510 ewf2_device_information: device_information,
3511 ewf2_case_data: case_data,
3512 })
3513}
3514
3515fn scan_ewf1_sections(file: &mut dyn SegmentReader) -> Result<Vec<Section>> {
3516 let file_len = file.segment_len()?;
3517 let mut sections = Vec::new();
3518 let mut offset = ewf1::FILE_HEADER_SIZE as u64;
3519 loop {
3520 let descriptor_end = offset
3521 .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
3522 .ok_or_else(|| EwfError::Malformed("EWF1 section descriptor overflow".into()))?;
3523 if descriptor_end > file_len {
3524 return Err(EwfError::Malformed(
3525 "EWF1 section descriptor exceeds file".into(),
3526 ));
3527 }
3528 let mut buf = [0; ewf1::SECTION_DESCRIPTOR_SIZE];
3529 file.seek(SeekFrom::Start(offset))?;
3530 file.read_exact(&mut buf)?;
3531 validate_present_adler32_checksum(&buf, 72, 72, "EWF1 section descriptor")?;
3532 let desc = ewf1::SectionDescriptor::parse(&buf, offset)?;
3533 let data_size = desc.data_size()?;
3534 let data_offset = descriptor_end;
3535 let data_end = data_offset
3536 .checked_add(data_size)
3537 .ok_or_else(|| EwfError::Malformed("EWF1 section data exceeds file".into()))?;
3538 if data_end > file_len {
3539 return Err(EwfError::Malformed("EWF1 section data exceeds file".into()));
3540 }
3541 let section_type = desc.section_type.clone();
3542 let next = desc.next;
3543 sections.push(Section {
3544 desc,
3545 data_offset,
3546 data_size,
3547 });
3548 if matches!(section_type.as_str(), "done" | "next") || next == 0 {
3549 return Ok(sections);
3550 }
3551 if next <= offset {
3552 return Err(EwfError::Malformed(
3553 "EWF1 section chain does not advance".into(),
3554 ));
3555 }
3556 if next < data_end {
3557 return Err(EwfError::Malformed(
3558 "EWF1 next section offset overlaps current section".into(),
3559 ));
3560 }
3561 offset = next;
3562 }
3563}
3564
3565fn ewf1_acquisition_complete(sections: &[Section]) -> bool {
3566 sections
3567 .last()
3568 .is_none_or(|section| section.desc.section_type != "next")
3569}
3570
3571fn scan_ewf2_sections(
3572 file: &mut dyn SegmentReader,
3573 strictness: OpenStrictness,
3574) -> Result<Vec<Ewf2Section>> {
3575 let file_len = file.segment_len()?;
3576 if file_len < ewf2::FILE_HEADER_SIZE as u64 + ewf2::SECTION_DESCRIPTOR_SIZE as u64 {
3577 return Err(EwfError::Malformed("EWF2 file is too short".into()));
3578 }
3579
3580 if let Some(sections) = scan_ewf2_leading_sections(file, file_len, strictness)? {
3581 return Ok(sections);
3582 }
3583 scan_ewf2_trailing_sections(file, file_len, strictness)
3584}
3585
3586fn scan_ewf2_leading_sections(
3587 file: &mut dyn SegmentReader,
3588 file_len: u64,
3589 strictness: OpenStrictness,
3590) -> Result<Option<Vec<Ewf2Section>>> {
3591 let mut sections = Vec::new();
3592 let mut offset = ewf2::FILE_HEADER_SIZE as u64;
3593
3594 loop {
3595 if offset
3596 .checked_add(ewf2::SECTION_DESCRIPTOR_SIZE as u64)
3597 .is_none_or(|end| end > file_len)
3598 {
3599 if sections.is_empty() {
3600 return Ok(None);
3601 }
3602 return Err(EwfError::Malformed(
3603 "EWF2 leading section descriptor exceeds file".into(),
3604 ));
3605 }
3606
3607 let mut buf = [0; ewf2::SECTION_DESCRIPTOR_SIZE];
3608 file.seek(SeekFrom::Start(offset))?;
3609 file.read_exact(&mut buf)?;
3610 let Ok(desc) = ewf2::SectionDescriptor::parse(&buf, offset) else {
3611 return if sections.is_empty() {
3612 Ok(None)
3613 } else {
3614 Err(EwfError::Malformed(
3615 "EWF2 leading section descriptor is invalid".into(),
3616 ))
3617 };
3618 };
3619 if !is_valid_ewf2_leading_descriptor(desc, strictness) {
3620 return if sections.is_empty() {
3621 Ok(None)
3622 } else {
3623 Err(EwfError::Malformed(
3624 "EWF2 leading section descriptor is invalid".into(),
3625 ))
3626 };
3627 }
3628 validate_present_adler32_checksum(&buf, 60, 60, "EWF2 section descriptor")?;
3629
3630 let data_offset = offset
3631 .checked_add(u64::from(desc.descriptor_size))
3632 .ok_or_else(|| EwfError::Malformed("EWF2 section data offset overflow".into()))?;
3633 let data_end = data_offset
3634 .checked_add(desc.data_size)
3635 .ok_or_else(|| EwfError::Malformed("EWF2 section advance overflow".into()))?;
3636 if data_end > file_len {
3637 return if sections.is_empty() {
3638 Ok(None)
3639 } else {
3640 Err(EwfError::Malformed("EWF2 section data exceeds file".into()))
3641 };
3642 }
3643 let padding_size = ewf2_section_padding_size(desc)?;
3644 let next_offset = data_end
3645 .checked_add(padding_size)
3646 .ok_or_else(|| EwfError::Malformed("EWF2 section padding overflow".into()))?;
3647 if next_offset > file_len {
3648 return Err(EwfError::Malformed(
3649 "EWF2 section padding exceeds file".into(),
3650 ));
3651 }
3652 reject_encrypted_ewf2_section(desc)?;
3653 validate_ewf2_section_integrity_hash(file, desc, data_offset)?;
3654 let section_type = desc.section_type;
3655 sections.push(Ewf2Section {
3656 desc,
3657 data_offset,
3658 data_size: desc.data_size,
3659 layout: Ewf2SectionLayout::LeadingDescriptor,
3660 });
3661 if is_terminal_ewf2_section(section_type) {
3662 return Ok(Some(sections));
3663 }
3664 if next_offset <= offset {
3665 return Err(EwfError::Malformed(
3666 "EWF2 leading section chain does not advance".into(),
3667 ));
3668 }
3669 offset = next_offset;
3670 }
3671}
3672
3673fn scan_ewf2_trailing_sections(
3674 file: &mut dyn SegmentReader,
3675 file_len: u64,
3676 strictness: OpenStrictness,
3677) -> Result<Vec<Ewf2Section>> {
3678 let mut sections = Vec::new();
3679 let header_size = ewf2::FILE_HEADER_SIZE as u64;
3680 let descriptor_size = ewf2::SECTION_DESCRIPTOR_SIZE as u64;
3681 let mut offset = file_len
3682 .checked_sub(descriptor_size)
3683 .ok_or_else(|| EwfError::Malformed("EWF2 file is too short".into()))?;
3684 let max_sections = ((file_len - header_size) / descriptor_size).saturating_add(1);
3685
3686 for _ in 0..max_sections {
3687 let mut buf = [0; ewf2::SECTION_DESCRIPTOR_SIZE];
3688 file.seek(SeekFrom::Start(offset))?;
3689 file.read_exact(&mut buf)?;
3690 let desc = ewf2::SectionDescriptor::parse(&buf, offset)?;
3691 if !is_valid_ewf2_descriptor(desc, strictness) {
3692 return Err(EwfError::Malformed(
3693 "EWF2 trailing section descriptor is invalid".into(),
3694 ));
3695 }
3696 validate_present_adler32_checksum(&buf, 60, 60, "EWF2 section descriptor")?;
3697 ewf2_section_padding_size(desc)?;
3698 let data_offset = if desc.previous_offset == 0 {
3699 header_size
3700 } else {
3701 desc.previous_offset
3702 .checked_add(descriptor_size)
3703 .ok_or_else(|| {
3704 EwfError::Malformed("EWF2 previous section offset overflow".into())
3705 })?
3706 };
3707 if data_offset < header_size {
3708 return Err(EwfError::Malformed(
3709 "EWF2 trailing section data precedes file header".into(),
3710 ));
3711 }
3712 let data_end = data_offset
3713 .checked_add(desc.data_size)
3714 .ok_or_else(|| EwfError::Malformed("EWF2 trailing section data overflow".into()))?;
3715 if data_end > offset {
3716 return Err(EwfError::Malformed(
3717 "EWF2 trailing section data exceeds descriptor".into(),
3718 ));
3719 }
3720 if desc.previous_offset != 0 {
3721 if desc.previous_offset >= desc.offset {
3722 return Err(EwfError::Malformed(
3723 "EWF2 previous section offset is not before current section".into(),
3724 ));
3725 }
3726 let previous_end = desc
3727 .previous_offset
3728 .checked_add(descriptor_size)
3729 .ok_or_else(|| {
3730 EwfError::Malformed("EWF2 previous section offset overflow".into())
3731 })?;
3732 if previous_end > offset {
3733 return Err(EwfError::Malformed(
3734 "EWF2 previous section overlaps current section descriptor".into(),
3735 ));
3736 }
3737 }
3738
3739 reject_encrypted_ewf2_section(desc)?;
3740 validate_ewf2_section_integrity_hash(file, desc, data_offset)?;
3741 let previous_offset = desc.previous_offset;
3742 sections.push(Ewf2Section {
3743 desc,
3744 data_offset,
3745 data_size: desc.data_size,
3746 layout: Ewf2SectionLayout::TrailingDescriptor,
3747 });
3748 if previous_offset == 0 {
3749 sections.reverse();
3750 return Ok(sections);
3751 }
3752 offset = previous_offset;
3753 }
3754
3755 Err(EwfError::Malformed(
3756 "EWF2 trailing section descriptor chain is too long".into(),
3757 ))
3758}
3759
3760fn is_valid_ewf2_descriptor(desc: ewf2::SectionDescriptor, strictness: OpenStrictness) -> bool {
3761 desc.descriptor_size == ewf2::SECTION_DESCRIPTOR_SIZE as u32
3762 && (strictness == OpenStrictness::Lenient
3763 || !matches!(desc.section_type, ewf2::SectionType::Unknown(_)))
3764}
3765
3766fn is_valid_ewf2_leading_descriptor(
3767 desc: ewf2::SectionDescriptor,
3768 strictness: OpenStrictness,
3769) -> bool {
3770 is_valid_ewf2_descriptor(desc, strictness)
3771}
3772
3773fn ewf2_section_padding_size(desc: ewf2::SectionDescriptor) -> Result<u64> {
3774 let padding_size = u64::from(desc.padding_size);
3775 if padding_size > desc.data_size {
3776 return Err(EwfError::Malformed(
3777 "EWF2 section padding size exceeds data size".into(),
3778 ));
3779 }
3780 Ok(padding_size)
3781}
3782
3783fn reject_encrypted_ewf2_section(desc: ewf2::SectionDescriptor) -> Result<()> {
3784 if desc.section_type == ewf2::SectionType::EncryptionKeys {
3785 return Err(EwfError::Unsupported(
3786 "encrypted EWF2 image with encryption keys section".into(),
3787 ));
3788 }
3789 if desc.encrypted {
3790 return Err(EwfError::Unsupported(format!(
3791 "encrypted EWF2 {:?} section",
3792 desc.section_type
3793 )));
3794 }
3795 Ok(())
3796}
3797
3798fn validate_ewf2_section_integrity_hash(
3799 file: &mut dyn SegmentReader,
3800 desc: ewf2::SectionDescriptor,
3801 data_offset: u64,
3802) -> Result<()> {
3803 if !desc.has_integrity_hash {
3804 return Ok(());
3805 }
3806
3807 let mut hasher = md5::Md5::new();
3808 let mut remaining = desc.data_size;
3809 let mut buffer = [0; 8192];
3810 file.seek(SeekFrom::Start(data_offset))?;
3811 while remaining > 0 {
3812 let take = usize::try_from(remaining.min(buffer.len() as u64))
3813 .expect("section hash read is bounded by buffer length");
3814 file.read_exact(&mut buffer[..take])?;
3815 md5::Digest::update(&mut hasher, &buffer[..take]);
3816 remaining -= u64::try_from(take).expect("usize fits u64");
3817 }
3818
3819 let calculated: [u8; 16] = hasher.finalize().into();
3820 if calculated != desc.data_integrity_hash {
3821 return Err(EwfError::Malformed(
3822 "EWF2 section data integrity hash mismatch".into(),
3823 ));
3824 }
3825
3826 Ok(())
3827}
3828
3829fn remember_ewf2_metadata_payload(
3830 target: &mut Option<Vec<u8>>,
3831 data: &[u8],
3832 label: &str,
3833) -> Result<()> {
3834 if let Some(existing) = target {
3835 if existing.as_slice() != data {
3836 return Err(EwfError::Malformed(format!("EWF2 {label} does not match")));
3837 }
3838 } else {
3839 *target = Some(data.to_vec());
3840 }
3841 Ok(())
3842}
3843
3844fn is_terminal_ewf2_section(section_type: ewf2::SectionType) -> bool {
3845 matches!(
3846 section_type,
3847 ewf2::SectionType::Done | ewf2::SectionType::Next
3848 )
3849}
3850
3851fn ewf2_acquisition_complete(sections: &[Ewf2Section]) -> bool {
3852 sections
3853 .last()
3854 .is_none_or(|section| section.desc.section_type != ewf2::SectionType::Next)
3855}
3856
3857fn parse_ewf2_memory_extents_table(data: &[u8]) -> Result<Vec<MemoryExtent>> {
3858 const ENTRY_SIZE: usize = 16;
3859
3860 if !data.len().is_multiple_of(ENTRY_SIZE) {
3861 return Err(EwfError::Malformed(
3862 "EWF2 memory extents table has partial entry".into(),
3863 ));
3864 }
3865
3866 Ok(data
3867 .as_chunks::<ENTRY_SIZE>()
3868 .0
3869 .iter()
3870 .map(|entry| MemoryExtent {
3871 start_page: u64::from_le_bytes(entry[0..8].try_into().expect("slice length checked")),
3872 page_count: u64::from_le_bytes(entry[8..16].try_into().expect("slice length checked")),
3873 })
3874 .collect())
3875}
3876
3877fn parse_ewf1_ltree_data(data: &[u8]) -> Result<SingleFilesInfo> {
3878 if data.len() < EWF1_LTREE_HEADER_SIZE {
3879 return Err(EwfError::Malformed(
3880 "EWF1 ltree section is too short".into(),
3881 ));
3882 }
3883
3884 let single_files_data_size =
3885 u64::from_le_bytes(data[16..24].try_into().expect("ltree header size checked"));
3886 let single_files_data_size = usize::try_from(single_files_data_size)
3887 .map_err(|_| EwfError::Malformed("EWF1 ltree data size does not fit usize".into()))?;
3888 let single_files_data_end = EWF1_LTREE_HEADER_SIZE
3889 .checked_add(single_files_data_size)
3890 .ok_or_else(|| EwfError::Malformed("EWF1 ltree data size overflow".into()))?;
3891 if single_files_data_end > data.len() {
3892 return Err(EwfError::Malformed(
3893 "EWF1 ltree data size exceeds section".into(),
3894 ));
3895 }
3896
3897 let stored = u32::from_le_bytes(data[24..28].try_into().expect("ltree header size checked"));
3898 let mut header = data[..EWF1_LTREE_HEADER_SIZE].to_vec();
3899 header[24..28].fill(0);
3900 validate_adler32_checksum_value(stored, &header, "EWF1 ltree header")?;
3901
3902 let text = &data[EWF1_LTREE_HEADER_SIZE..single_files_data_end];
3903 let computed = md5::Md5::digest(text);
3904 if computed[..] != data[..16] {
3905 return Err(EwfError::Malformed("EWF1 ltree text MD5 mismatch".into()));
3906 }
3907 parse_ewf1_single_files_data(text)
3908}
3909
3910fn parse_ewf2_single_files_aux_u64_table(data: &[u8], label: &str) -> Result<Vec<u64>> {
3911 parse_ewf2_single_files_aux_table(data, 8, label, |entry| {
3912 u64::from_le_bytes(entry.try_into().expect("entry size checked"))
3913 })
3914}
3915
3916fn parse_ewf2_single_files_md5_hash_table(data: &[u8]) -> Result<Vec<[u8; 16]>> {
3917 parse_ewf2_single_files_aux_table(data, 16, "EWF2 single files MD5 hash table", |entry| {
3918 entry.try_into().expect("entry size checked")
3919 })
3920}
3921
3922fn parse_ewf2_single_files_aux_table<T>(
3923 data: &[u8],
3924 entry_size: usize,
3925 label: &str,
3926 parse_entry: impl Fn(&[u8]) -> T,
3927) -> Result<Vec<T>> {
3928 const PADDED_HEADER_SIZE: usize = 32;
3929 const FOOTER_SIZE: usize = 4;
3930
3931 if data.len() < PADDED_HEADER_SIZE + FOOTER_SIZE {
3932 return Err(EwfError::Malformed(format!("{label} is too short")));
3933 }
3934
3935 validate_present_adler32_checksum(data, 16, 16, &format!("{label} header"))?;
3936 let entry_count = u32::from_le_bytes(data[0..4].try_into().expect("slice length checked"));
3937 let entry_bytes = usize::try_from(entry_count)
3938 .map_err(|_| EwfError::Malformed(format!("{label} entry count does not fit usize")))?
3939 .checked_mul(entry_size)
3940 .ok_or_else(|| EwfError::Malformed(format!("{label} entry bytes overflow")))?;
3941 let entries_offset = PADDED_HEADER_SIZE;
3942 let entries_end = entries_offset
3943 .checked_add(entry_bytes)
3944 .ok_or_else(|| EwfError::Malformed(format!("{label} entry range overflow")))?;
3945 let footer_end = entries_end
3946 .checked_add(FOOTER_SIZE)
3947 .ok_or_else(|| EwfError::Malformed(format!("{label} footer range overflow")))?;
3948 if footer_end > data.len() {
3949 return Err(EwfError::Malformed(format!(
3950 "{label} entries exceed section"
3951 )));
3952 }
3953
3954 let stored = u32::from_le_bytes(
3955 data[entries_end..entries_end + FOOTER_SIZE]
3956 .try_into()
3957 .expect("footer range checked"),
3958 );
3959 if stored != 0 {
3960 validate_adler32_checksum_value(stored, &data[entries_offset..entries_end], label)?;
3961 }
3962 Ok(data[entries_offset..entries_end]
3963 .chunks_exact(entry_size)
3964 .map(parse_entry)
3965 .collect())
3966}
3967
3968fn parse_ewf1_ranges(
3969 file: &mut dyn SegmentReader,
3970 sections: &[Section],
3971 segment_index: usize,
3972 first_chunk: u64,
3973 logical_size: u64,
3974 profile: Ewf1RangeProfile,
3975 statistics: &ReaderStatisticsCollector,
3976) -> Result<Vec<TableRange>> {
3977 let mut ranges = Vec::new();
3978 let mut next_chunk = first_chunk;
3979 let mut previous_table: Option<(bool, u32, u64)> = None;
3980 for section in sections
3981 .iter()
3982 .filter(|section| matches!(section.desc.section_type.as_str(), "table" | "table2"))
3983 {
3984 if section.data_size < 24 {
3985 return Err(EwfError::Malformed(
3986 "EWF1 table section is too short".into(),
3987 ));
3988 }
3989 let data = read_exact_at(file, section.data_offset, 24)?;
3990 validate_present_adler32_checksum(&data, 20, 20, "EWF1 table header")?;
3991 let entry_count = u32::from_le_bytes(data[0..4].try_into().expect("slice length checked"));
3992 let base_offset = u64::from_le_bytes(data[8..16].try_into().expect("slice length checked"));
3993 if entry_count == 0 {
3994 continue;
3995 }
3996 if section.desc.section_type == "table2"
3997 && previous_table.is_some_and(|(previous_was_table, previous_count, previous_base)| {
3998 previous_was_table && previous_count == entry_count && previous_base == base_offset
3999 })
4000 {
4001 continue;
4002 }
4003
4004 let entry_bytes = u64::from(entry_count)
4005 .checked_mul(4)
4006 .ok_or_else(|| EwfError::Malformed("EWF1 table entry bytes overflow".into()))?;
4007 let entries_offset = section
4008 .data_offset
4009 .checked_add(24)
4010 .ok_or_else(|| EwfError::Malformed("EWF1 table entry offset overflow".into()))?;
4011 let entries_end = entries_offset
4012 .checked_add(entry_bytes)
4013 .ok_or_else(|| EwfError::Malformed("EWF1 table entry range overflow".into()))?;
4014 let section_end = section
4015 .data_offset
4016 .checked_add(section.data_size)
4017 .ok_or_else(|| EwfError::Malformed("EWF1 table section range overflow".into()))?;
4018 if entries_end > section_end {
4019 return Err(EwfError::Malformed(
4020 "EWF1 table entries exceed section".into(),
4021 ));
4022 }
4023 let first_entry_offset = if base_offset == 0 {
4024 let raw = read_exact_at(file, entries_offset, 4)?;
4025 Some(u64::from(
4026 u32::from_le_bytes(raw.try_into().expect("first table entry read size checked"))
4027 & 0x7fff_ffff,
4028 ))
4029 } else {
4030 None
4031 };
4032 let table_resident_without_entries_checksum =
4033 first_entry_offset.is_some_and(|offset| offset == entries_end);
4034 if entries_end
4035 .checked_add(4)
4036 .is_some_and(|footer_end| footer_end <= section_end)
4037 && !table_resident_without_entries_checksum
4038 {
4039 validate_present_table_entries_checksum(
4040 file,
4041 entries_offset,
4042 entry_bytes,
4043 entries_end,
4044 "EWF1 table entries",
4045 statistics,
4046 )?;
4047 }
4048
4049 let (data_base, data_end) = if let Some(sectors) =
4050 matching_sectors_section(sections, base_offset).or_else(|| {
4051 first_entry_offset
4052 .and_then(|offset| sectors_section_containing_offset(sections, offset))
4053 }) {
4054 let data_end = sectors
4055 .data_offset
4056 .checked_add(sectors.data_size)
4057 .ok_or_else(|| EwfError::Malformed("EWF1 sectors range overflow".into()))?;
4058 (base_offset, data_end)
4059 } else {
4060 let resident_data_start = if table_resident_without_entries_checksum {
4061 entries_end
4062 } else {
4063 entries_end.checked_add(4).ok_or_else(|| {
4064 EwfError::Malformed("EWF1 table-resident data offset overflow".into())
4065 })?
4066 };
4067 if resident_data_start > section_end {
4068 return Err(EwfError::Malformed(
4069 "EWF1 table-resident data starts beyond table section".into(),
4070 ));
4071 }
4072 (0, section_end)
4073 };
4074
4075 ranges.push(TableRange {
4076 kind: TableRangeKind::Ewf1,
4077 segment_index,
4078 first_chunk: next_chunk,
4079 chunk_count: u64::from(entry_count),
4080 entries_offset,
4081 base_offset: data_base,
4082 data_end: Some(data_end),
4083 ewf1_allow_large_compressed_chunks: profile.allow_large_compressed_chunks,
4084 ewf1_compression_method: Some(profile.compression_method),
4085 ewf2_compression_method: None,
4086 });
4087 next_chunk = next_chunk
4088 .checked_add(u64::from(entry_count))
4089 .ok_or_else(|| EwfError::Malformed("EWF1 table chunk count overflow".into()))?;
4090 previous_table = Some((
4091 section.desc.section_type == "table",
4092 entry_count,
4093 base_offset,
4094 ));
4095 }
4096
4097 if ranges.is_empty() && logical_size > 0 {
4098 return Err(EwfError::Malformed("EWF1 table coverage is missing".into()));
4099 }
4100 Ok(ranges)
4101}
4102
4103fn matching_sectors_section(sections: &[Section], base_offset: u64) -> Option<&Section> {
4104 sections
4105 .iter()
4106 .filter(|section| section.desc.section_type == "sectors")
4107 .find(|section| {
4108 base_offset == section.desc.offset
4109 || sectors_section_contains_offset(section, base_offset)
4110 })
4111}
4112
4113fn sectors_section_containing_offset(sections: &[Section], offset: u64) -> Option<&Section> {
4114 sections
4115 .iter()
4116 .filter(|section| section.desc.section_type == "sectors")
4117 .find(|section| sectors_section_contains_offset(section, offset))
4118}
4119
4120fn sectors_section_contains_offset(section: &Section, offset: u64) -> bool {
4121 section
4122 .data_offset
4123 .checked_add(section.data_size)
4124 .is_some_and(|data_end| offset >= section.data_offset && offset <= data_end)
4125}
4126
4127fn parse_ewf2_ranges(
4128 file: &mut dyn SegmentReader,
4129 sections: &[Ewf2Section],
4130 segment_index: usize,
4131 logical_size: u64,
4132 compression_method: ewf2::CompressionMethod,
4133 statistics: &ReaderStatisticsCollector,
4134) -> Result<Vec<TableRange>> {
4135 let mut ranges = Vec::new();
4136 for section in sections
4137 .iter()
4138 .filter(|section| section.desc.section_type == ewf2::SectionType::SectorTable)
4139 {
4140 let minimum_table_header_size = match section.layout {
4141 Ewf2SectionLayout::LeadingDescriptor => ewf2::TABLE_HEADER_SIZE as u64,
4142 Ewf2SectionLayout::TrailingDescriptor => EWF2_TABLE_HEADER_V2_SIZE,
4143 };
4144 if section.data_size < minimum_table_header_size {
4145 return Err(EwfError::Malformed("EWF2 sector table is too short".into()));
4146 }
4147 let header_read_size = if section.data_size >= EWF2_TABLE_HEADER_V2_SIZE {
4148 EWF2_TABLE_HEADER_V2_SIZE
4149 } else {
4150 minimum_table_header_size
4151 };
4152 let data = read_exact_at(file, section.data_offset, header_read_size)?;
4153 validate_present_adler32_checksum(&data, 16, 16, "EWF2 table header")?;
4154 let header = ewf2::TableHeader::parse(&data[..ewf2::TABLE_HEADER_SIZE])?;
4155 let entry_count = u64::from(header.entry_count);
4156 if entry_count == 0 {
4157 continue;
4158 }
4159 let entries_bytes = entry_count
4160 .checked_mul(ewf2::TABLE_ENTRY_SIZE as u64)
4161 .ok_or_else(|| EwfError::Malformed("EWF2 table entry bytes overflow".into()))?;
4162 let table_header_and_padding_size = match section.layout {
4163 Ewf2SectionLayout::TrailingDescriptor => EWF2_TABLE_HEADER_V2_SIZE,
4164 Ewf2SectionLayout::LeadingDescriptor => {
4165 let full_header_entries_size = EWF2_TABLE_HEADER_V2_SIZE
4166 .checked_add(entries_bytes)
4167 .ok_or_else(|| EwfError::Malformed("EWF2 table size overflow".into()))?;
4168 if section.data_size >= full_header_entries_size {
4169 EWF2_TABLE_HEADER_V2_SIZE
4170 } else {
4171 ewf2::TABLE_HEADER_SIZE as u64
4172 }
4173 }
4174 };
4175 let entries_offset = section
4176 .data_offset
4177 .checked_add(table_header_and_padding_size)
4178 .ok_or_else(|| EwfError::Malformed("EWF2 table entry offset overflow".into()))?;
4179 let entries_end = entries_offset
4180 .checked_add(entries_bytes)
4181 .ok_or_else(|| EwfError::Malformed("EWF2 table entry range overflow".into()))?;
4182 let section_end = section
4183 .data_offset
4184 .checked_add(section.data_size)
4185 .ok_or_else(|| EwfError::Malformed("EWF2 table section range overflow".into()))?;
4186 if entries_end > section_end {
4187 return Err(EwfError::Malformed(
4188 "EWF2 table entries exceed section".into(),
4189 ));
4190 }
4191 if entries_end
4192 .checked_add(EWF2_TABLE_FOOTER_SIZE)
4193 .is_some_and(|footer_end| footer_end <= section_end)
4194 {
4195 validate_present_table_entries_checksum(
4196 file,
4197 entries_offset,
4198 entries_bytes,
4199 entries_end,
4200 "EWF2 table entries",
4201 statistics,
4202 )?;
4203 }
4204
4205 ranges.push(TableRange {
4206 kind: TableRangeKind::Ewf2,
4207 segment_index,
4208 first_chunk: header.first_chunk,
4209 chunk_count: entry_count,
4210 entries_offset,
4211 base_offset: 0,
4212 data_end: None,
4213 ewf1_allow_large_compressed_chunks: false,
4214 ewf1_compression_method: None,
4215 ewf2_compression_method: Some(compression_method_code(compression_method)),
4216 });
4217 }
4218
4219 if ranges.is_empty() && logical_size > 0 {
4220 return Err(EwfError::Malformed("EWF2 image has no sector table".into()));
4221 }
4222 Ok(ranges)
4223}
4224
4225fn read_exact_at(file: &mut dyn SegmentReader, offset: u64, size: u64) -> Result<Vec<u8>> {
4226 let mut data = vec![
4227 0;
4228 usize::try_from(size).map_err(|_| {
4229 EwfError::Malformed("read size does not fit usize".into())
4230 })?
4231 ];
4232 file.seek(SeekFrom::Start(offset))?;
4233 file.read_exact(&mut data)?;
4234 Ok(data)
4235}
4236
4237#[cfg(unix)]
4238fn read_file_at(file: &File, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
4239 std::os::unix::fs::FileExt::read_at(file, buffer, offset)
4240}
4241
4242#[cfg(windows)]
4243fn read_file_at(file: &File, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
4244 std::os::windows::fs::FileExt::seek_read(file, buffer, offset)
4245}
4246
4247#[cfg(not(any(unix, windows)))]
4248fn read_file_at(file: &File, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
4249 let mut file = file.try_clone()?;
4250 file.seek(SeekFrom::Start(offset))?;
4251 file.read(buffer)
4252}
4253
4254fn read_file_exact_at(file: &File, buffer: &mut [u8], mut offset: u64) -> io::Result<()> {
4255 let mut remaining = buffer;
4256 while !remaining.is_empty() {
4257 match read_file_at(file, remaining, offset) {
4258 Ok(0) => {
4259 return Err(io::Error::new(
4260 io::ErrorKind::UnexpectedEof,
4261 "segment file ended before requested range",
4262 ));
4263 }
4264 Ok(count) => {
4265 offset = offset.saturating_add(count as u64);
4266 remaining = &mut remaining[count..];
4267 }
4268 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
4269 Err(error) => return Err(error),
4270 }
4271 }
4272 Ok(())
4273}
4274
4275fn table_entry_offset(range: &TableRange, local_index: u64, entry_size: u64) -> Result<u64> {
4276 range
4277 .entries_offset
4278 .checked_add(
4279 local_index
4280 .checked_mul(entry_size)
4281 .ok_or_else(|| EwfError::Malformed("table entry offset overflow".into()))?,
4282 )
4283 .ok_or_else(|| EwfError::Malformed("table entry offset overflow".into()))
4284}
4285
4286fn validate_ewf1_encoded_size(
4287 encoded_size: u64,
4288 chunk_size: u64,
4289 encoding: ChunkEncoding,
4290 allow_large_compressed_chunks: bool,
4291) -> Result<()> {
4292 if encoding == ChunkEncoding::Zlib {
4293 let standard_maximum = zlib_compressed_chunk_size_cap(chunk_size)?;
4294 if !allow_large_compressed_chunks && encoded_size <= standard_maximum {
4295 return validate_encoded_size(encoded_size, chunk_size, encoding);
4296 }
4297 if encoded_size == 0 {
4298 return Err(EwfError::Malformed("chunk data size is zero".into()));
4299 }
4300 let maximum = chunk_size
4301 .checked_mul(2)
4302 .ok_or_else(|| EwfError::Malformed("EWF1 chunk size cap overflow".into()))?;
4303 if encoded_size > maximum {
4304 return Err(EwfError::Malformed(format!(
4305 "EWF1 compressed chunk size {encoded_size} exceeds maximum {maximum}"
4306 )));
4307 }
4308 Ok(())
4309 } else {
4310 validate_encoded_size(encoded_size, chunk_size, encoding)
4311 }
4312}
4313
4314fn ewf1_chunk_encoding(
4315 entry_compressed: bool,
4316 encoded_size: u64,
4317 chunk_size: u64,
4318 compression_method: CompressionMethod,
4319) -> Result<ChunkEncoding> {
4320 if entry_compressed {
4321 return match compression_method {
4322 CompressionMethod::Zlib => Ok(ChunkEncoding::Zlib),
4323 CompressionMethod::Zstd => Ok(ChunkEncoding::Zstd),
4324 method => Err(EwfError::Unsupported(format!(
4325 "unsupported EWF1 compression method {method:?}"
4326 ))),
4327 };
4328 }
4329 if compression_method == CompressionMethod::Zlib
4330 && encoded_size > raw_chunk_size_cap(chunk_size)?
4331 && encoded_size <= zlib_compressed_chunk_size_cap(chunk_size)?
4332 {
4333 return Ok(ChunkEncoding::Zlib);
4334 }
4335 Ok(ChunkEncoding::Raw)
4336}
4337
4338fn decode_ewf1_entry(
4339 range: &TableRange,
4340 raw: u32,
4341 chunk_size: u64,
4342 next_raw: Option<u32>,
4343 is_final: bool,
4344) -> Result<Ewf1DecodedEntry> {
4345 let entry = ewf1::TableEntry::parse(&raw.to_le_bytes())?;
4346 let masked_offset = range
4347 .base_offset
4348 .checked_add(entry.offset)
4349 .ok_or_else(|| EwfError::Malformed("EWF1 chunk offset overflow".into()))?;
4350
4351 if should_use_full_width_ewf1_offset(range, raw, masked_offset, chunk_size, next_raw, is_final)?
4352 {
4353 return Ok(Ewf1DecodedEntry {
4354 compressed: false,
4355 offset: range
4356 .base_offset
4357 .checked_add(u64::from(raw))
4358 .ok_or_else(|| EwfError::Malformed("EWF1 chunk offset overflow".into()))?,
4359 });
4360 }
4361
4362 Ok(Ewf1DecodedEntry {
4363 compressed: entry.compressed,
4364 offset: masked_offset,
4365 })
4366}
4367
4368fn should_use_full_width_ewf1_offset(
4369 range: &TableRange,
4370 raw: u32,
4371 masked_offset: u64,
4372 chunk_size: u64,
4373 next_raw: Option<u32>,
4374 is_final: bool,
4375) -> Result<bool> {
4376 if raw & 0x8000_0000 == 0 {
4377 return Ok(false);
4378 }
4379 let Some(data_end) = range.data_end else {
4380 return Ok(false);
4381 };
4382 let data_len = data_end
4383 .checked_sub(range.base_offset)
4384 .ok_or_else(|| EwfError::Malformed("EWF1 data region precedes base offset".into()))?;
4385 if data_len <= 0x8000_0000 {
4386 return Ok(false);
4387 }
4388
4389 let full_offset = range
4390 .base_offset
4391 .checked_add(u64::from(raw))
4392 .ok_or_else(|| EwfError::Malformed("EWF1 chunk offset overflow".into()))?;
4393 if full_offset >= data_end {
4394 return Ok(false);
4395 }
4396
4397 if let Some(next_raw) = next_raw {
4398 let next_masked = range
4399 .base_offset
4400 .checked_add(u64::from(next_raw & 0x7fff_ffff))
4401 .ok_or_else(|| EwfError::Malformed("EWF1 next chunk offset overflow".into()))?;
4402 let masked_is_valid = next_masked > masked_offset
4403 && next_masked - masked_offset <= zlib_compressed_chunk_size_cap(chunk_size)?;
4404 if masked_is_valid {
4405 return Ok(false);
4406 }
4407
4408 let next_full = if next_raw & 0x8000_0000 != 0 {
4409 range
4410 .base_offset
4411 .checked_add(u64::from(next_raw))
4412 .ok_or_else(|| EwfError::Malformed("EWF1 next chunk offset overflow".into()))?
4413 } else {
4414 next_masked
4415 };
4416 return Ok(
4417 next_full > full_offset && next_full - full_offset <= raw_chunk_size_cap(chunk_size)?
4418 );
4419 }
4420
4421 if is_final {
4422 let masked_size = data_end.saturating_sub(masked_offset);
4423 let full_size = data_end - full_offset;
4424 return Ok(masked_size > zlib_compressed_chunk_size_cap(chunk_size)?
4425 && full_size <= raw_chunk_size_cap(chunk_size)?);
4426 }
4427
4428 Ok(false)
4429}
4430
4431fn logical_chunk_size(logical_size: u64, chunk_size: u64, chunk_id: u64) -> Result<usize> {
4432 let logical_offset = chunk_id
4433 .checked_mul(chunk_size)
4434 .ok_or_else(|| EwfError::Malformed("logical chunk offset overflow".into()))?;
4435 let size = logical_size.saturating_sub(logical_offset).min(chunk_size);
4436 usize::try_from(size)
4437 .map_err(|_| EwfError::Malformed("logical chunk size does not fit usize".into()))
4438}
4439
4440fn checksum_error_range(
4441 info: &ImageInfo,
4442 chunk_id: u64,
4443 logical_size: usize,
4444) -> Result<SectorRange> {
4445 let logical_offset = chunk_id
4446 .checked_mul(info.chunk_size)
4447 .ok_or_else(|| EwfError::Malformed("checksum error logical offset overflow".into()))?;
4448 let logical_size = u64::try_from(logical_size)
4449 .map_err(|_| EwfError::Malformed("checksum error size does not fit u64".into()))?;
4450 let Some(bytes_per_sector) = info.media.bytes_per_sector.filter(|value| *value != 0) else {
4451 return Ok(SectorRange {
4452 first_sector: logical_offset,
4453 sector_count: logical_size.max(1),
4454 });
4455 };
4456
4457 Ok(SectorRange {
4458 first_sector: logical_offset / bytes_per_sector,
4459 sector_count: logical_size.div_ceil(bytes_per_sector).max(1),
4460 })
4461}
4462
4463fn validate_chunk_size(chunk_size: u64) -> Result<()> {
4464 if chunk_size == 0 {
4465 return Err(EwfError::Malformed("chunk size is zero".into()));
4466 }
4467 if chunk_size > MAX_CHUNK_SIZE {
4468 return Err(EwfError::Malformed(format!(
4469 "chunk size {chunk_size} exceeds maximum {MAX_CHUNK_SIZE}"
4470 )));
4471 }
4472 Ok(())
4473}
4474
4475fn apply_ewf2_geometry(media: &mut MediaInfo, geometry: Ewf2Geometry) {
4476 if let Some(value) = geometry.sectors_per_chunk {
4477 media.sectors_per_chunk = Some(value);
4478 }
4479 if let Some(value) = geometry.bytes_per_sector {
4480 media.bytes_per_sector = Some(value);
4481 }
4482 if let Some(value) = geometry.sector_count {
4483 media.sector_count = Some(value);
4484 }
4485 if let Some(value) = geometry.chunk_count {
4486 media.chunk_count = Some(value);
4487 }
4488 if let Some(value) = geometry.error_granularity {
4489 media.error_granularity = Some(value);
4490 }
4491 if let Some(value) = geometry.media_type {
4492 media.media_type = Some(value);
4493 }
4494 if let Some(value) = geometry.physical {
4495 media.media_flags.physical = value;
4496 }
4497 media.media_flags.fastbloc |= geometry.fastbloc;
4498 media.media_flags.tableau |= geometry.tableau;
4499}
4500
4501fn apply_ewf2_geometry_if_missing(media: &mut MediaInfo, geometry: Ewf2Geometry) {
4502 if media.sectors_per_chunk.is_none() {
4503 media.sectors_per_chunk = geometry.sectors_per_chunk;
4504 }
4505 if media.bytes_per_sector.is_none() {
4506 media.bytes_per_sector = geometry.bytes_per_sector;
4507 }
4508 if media.sector_count.is_none() {
4509 media.sector_count = geometry.sector_count;
4510 }
4511 if media.chunk_count.is_none() {
4512 media.chunk_count = geometry.chunk_count;
4513 }
4514 if media.error_granularity.is_none() {
4515 media.error_granularity = geometry.error_granularity;
4516 }
4517 if media.media_type.is_none() {
4518 media.media_type = geometry.media_type;
4519 }
4520 if let Some(value) = geometry.physical {
4521 media.media_flags.physical = value;
4522 }
4523 media.media_flags.fastbloc |= geometry.fastbloc;
4524 media.media_flags.tableau |= geometry.tableau;
4525}
4526
4527fn public_compression_method(method: ewf2::CompressionMethod) -> CompressionMethod {
4528 match method {
4529 ewf2::CompressionMethod::None => CompressionMethod::None,
4530 ewf2::CompressionMethod::Zlib => CompressionMethod::Zlib,
4531 ewf2::CompressionMethod::Bzip2 => CompressionMethod::Bzip2,
4532 ewf2::CompressionMethod::Unknown(method) => CompressionMethod::Unknown(method),
4533 }
4534}
4535
4536fn compression_method_code(method: ewf2::CompressionMethod) -> u16 {
4537 match method {
4538 ewf2::CompressionMethod::None => 0,
4539 ewf2::CompressionMethod::Zlib => 1,
4540 ewf2::CompressionMethod::Bzip2 => 2,
4541 ewf2::CompressionMethod::Unknown(method) => method,
4542 }
4543}
4544
4545fn merge_hashes(target: &mut StoredHashes, source: &StoredHashes) -> Result<()> {
4546 if let Some(hash) = source.md5 {
4547 insert_hash_value(target, "MD5", &hash)?;
4548 }
4549 if let Some(hash) = source.sha1 {
4550 insert_hash_value(target, "SHA1", &hash)?;
4551 }
4552 for (identifier, value) in &source.hash_values {
4553 crate::hashes::insert_stored_hash(target, identifier, value)?;
4554 }
4555 Ok(())
4556}
4557
4558fn merge_segment_format_profile(
4559 target: &mut Option<FormatProfile>,
4560 target_hint_only: &mut bool,
4561 source: FormatProfile,
4562 source_hint_only: bool,
4563) -> Result<()> {
4564 if source == FormatProfile::Unknown {
4565 return Ok(());
4566 }
4567 match target {
4568 Some(existing) if *existing == FormatProfile::Unknown => {
4569 *existing = source;
4570 *target_hint_only = source_hint_only;
4571 }
4572 Some(existing) if *existing == source => {
4573 if !source_hint_only {
4574 *target_hint_only = false;
4575 }
4576 }
4577 Some(existing)
4578 if *target_hint_only
4579 && !source_hint_only
4580 && ewf1_profile_hint_matches_detected_profile(*existing, source) =>
4581 {
4582 *existing = source;
4583 *target_hint_only = false;
4584 }
4585 Some(existing)
4586 if !*target_hint_only
4587 && source_hint_only
4588 && ewf1_profile_hint_matches_detected_profile(source, *existing) => {}
4589 Some(existing) if *existing != source => {
4590 return Err(EwfError::Malformed(format!(
4591 "segment format profiles do not match ({existing:?} != {source:?})",
4592 )));
4593 }
4594 Some(_) => {}
4595 None => {
4596 *target = Some(source);
4597 *target_hint_only = source_hint_only;
4598 }
4599 }
4600 Ok(())
4601}
4602
4603fn ewf1_profile_hint_matches_detected_profile(
4604 hint: FormatProfile,
4605 detected: FormatProfile,
4606) -> bool {
4607 matches!(
4608 hint,
4609 FormatProfile::EnCase2 | FormatProfile::EnCase5 | FormatProfile::Ewf
4610 ) && matches!(
4611 detected,
4612 FormatProfile::EnCase1
4613 | FormatProfile::EnCase2
4614 | FormatProfile::EnCase3
4615 | FormatProfile::EnCase4
4616 | FormatProfile::EnCase5
4617 | FormatProfile::EnCase6
4618 | FormatProfile::EnCase7
4619 | FormatProfile::FtkImager
4620 | FormatProfile::Linen5
4621 | FormatProfile::Linen6
4622 | FormatProfile::Linen7
4623 )
4624}
4625
4626fn apply_detected_ewf1_format_profile(
4627 target: &mut FormatProfile,
4628 detected: Option<FormatProfile>,
4629) -> bool {
4630 let Some(detected) = detected.filter(|profile| *profile != FormatProfile::Unknown) else {
4631 return false;
4632 };
4633 if *target == FormatProfile::Smart && detected == FormatProfile::FtkImager {
4634 *target = detected;
4635 return true;
4636 }
4637 if matches!(
4638 target,
4639 FormatProfile::Smart
4640 | FormatProfile::LogicalEnCase5
4641 | FormatProfile::LogicalEnCase6
4642 | FormatProfile::LogicalEnCase7
4643 ) {
4644 return false;
4645 }
4646 *target = detected;
4647 true
4648}
4649
4650fn merge_acquisition_errors(
4651 target: &mut Vec<AcquisitionError>,
4652 source: Vec<AcquisitionError>,
4653) -> Result<()> {
4654 if source.is_empty() {
4655 return Ok(());
4656 }
4657 target.extend(source);
4658 target.sort_unstable_by_key(|range| range.first_sector);
4659 let mut merged: Vec<AcquisitionError> = Vec::with_capacity(target.len());
4660 for range in target.drain(..) {
4661 let end = range
4662 .first_sector
4663 .checked_add(range.sector_count)
4664 .ok_or_else(|| EwfError::Malformed("acquisition error range overflow".into()))?;
4665 if let Some(previous) = merged.last_mut()
4666 && range.first_sector <= previous.first_sector + previous.sector_count
4667 {
4668 previous.sector_count =
4669 end.max(previous.first_sector + previous.sector_count) - previous.first_sector;
4670 } else {
4671 merged.push(range);
4672 }
4673 }
4674 *target = merged;
4675 Ok(())
4676}
4677
4678fn merge_single_files(
4679 target: &mut Option<SingleFilesInfo>,
4680 source: Option<SingleFilesInfo>,
4681) -> Result<()> {
4682 if let Some(source) = source {
4683 if target.is_some() {
4684 return Err(EwfError::Malformed(
4685 "EWF2 image has duplicate single files data sections".into(),
4686 ));
4687 }
4688 *target = Some(source);
4689 }
4690 Ok(())
4691}
4692
4693fn merge_single_files_aux_tables(
4694 target: &mut SingleFilesAuxTables,
4695 source: SingleFilesAuxTables,
4696) -> Result<()> {
4697 merge_single_files_aux_u64_table(
4698 &mut target.table_0x21_entries,
4699 source.table_0x21_entries,
4700 "0x21",
4701 )?;
4702 merge_single_files_aux_md5_table(&mut target.md5_hashes, source.md5_hashes)?;
4703 merge_single_files_aux_u64_table(
4704 &mut target.table_0x23_entries,
4705 source.table_0x23_entries,
4706 "0x23",
4707 )
4708}
4709
4710fn merge_single_files_aux_u64_table(
4711 target: &mut Vec<u64>,
4712 source: Vec<u64>,
4713 table_name: &str,
4714) -> Result<()> {
4715 if source.is_empty() {
4716 return Ok(());
4717 }
4718 if !target.is_empty() {
4719 return Err(EwfError::Malformed(format!(
4720 "EWF2 image has duplicate single files {table_name} table sections"
4721 )));
4722 }
4723 *target = source;
4724 Ok(())
4725}
4726
4727fn merge_single_files_aux_md5_table(
4728 target: &mut Vec<[u8; 16]>,
4729 source: Vec<[u8; 16]>,
4730) -> Result<()> {
4731 if source.is_empty() {
4732 return Ok(());
4733 }
4734 if !target.is_empty() {
4735 return Err(EwfError::Malformed(
4736 "EWF2 image has duplicate single files MD5 hash table sections".into(),
4737 ));
4738 }
4739 *target = source;
4740 Ok(())
4741}
4742
4743fn merge_optional_ewf2_string_section(
4744 target: &mut Option<String>,
4745 source: Option<String>,
4746 label: &str,
4747) -> Result<()> {
4748 let Some(source) = source else {
4749 return Ok(());
4750 };
4751 if target.is_some() {
4752 return Err(EwfError::Malformed(format!(
4753 "EWF2 image has duplicate {label} sections"
4754 )));
4755 }
4756 *target = Some(source);
4757 Ok(())
4758}
4759
4760fn merge_optional_ewf2_raw_section(
4761 target: &mut Option<Vec<u8>>,
4762 source: Option<Vec<u8>>,
4763 label: &str,
4764) -> Result<()> {
4765 let Some(source) = source else {
4766 return Ok(());
4767 };
4768 if target.is_some() {
4769 return Err(EwfError::Malformed(format!(
4770 "EWF2 image has duplicate {label} sections"
4771 )));
4772 }
4773 *target = Some(source);
4774 Ok(())
4775}
4776
4777fn parse_nonzero_hash<const N: usize>(data: &[u8]) -> Option<[u8; N]> {
4778 let value = data.get(..N)?;
4779 let mut hash = [0; N];
4780 hash.copy_from_slice(value);
4781 hash.iter().any(|byte| *byte != 0).then_some(hash)
4782}
4783
4784fn validate_adler32_checksum(
4785 data: &[u8],
4786 checksum_offset: usize,
4787 checksum_data_size: usize,
4788 label: &str,
4789) -> Result<()> {
4790 let stored = u32::from_le_bytes(
4791 data[checksum_offset..checksum_offset + 4]
4792 .try_into()
4793 .expect("hash section size checked"),
4794 );
4795 validate_adler32_checksum_value(stored, &data[..checksum_data_size], label)
4796}
4797
4798fn validate_present_adler32_checksum(
4799 data: &[u8],
4800 checksum_offset: usize,
4801 checksum_data_size: usize,
4802 label: &str,
4803) -> Result<()> {
4804 let stored = u32::from_le_bytes(
4805 data[checksum_offset..checksum_offset + 4]
4806 .try_into()
4807 .expect("checksum offset checked"),
4808 );
4809 if stored == 0 {
4810 return Ok(());
4811 }
4812 validate_adler32_checksum_value(stored, &data[..checksum_data_size], label)
4813}
4814
4815fn validate_present_table_entries_checksum(
4816 file: &mut dyn SegmentReader,
4817 entries_offset: u64,
4818 entry_bytes: u64,
4819 checksum_offset: u64,
4820 label: &str,
4821 statistics: &ReaderStatisticsCollector,
4822) -> Result<()> {
4823 let checksum = read_exact_at(file, checksum_offset, 4)?;
4824 let stored = u32::from_le_bytes(
4825 checksum
4826 .as_slice()
4827 .try_into()
4828 .expect("checksum size checked"),
4829 );
4830 if stored == 0 {
4831 return Ok(());
4832 }
4833 let started = statistics.enabled().then(Instant::now);
4834 let calculated = stream_adler32(file, entries_offset, entry_bytes)?;
4835 if let Some(started) = started {
4836 statistics.record_table_checksum(entry_bytes, started.elapsed());
4837 }
4838 if stored != calculated {
4839 return Err(EwfError::Malformed(format!("{label} checksum mismatch")));
4840 }
4841 Ok(())
4842}
4843
4844fn stream_adler32(file: &mut dyn SegmentReader, offset: u64, size: u64) -> Result<u32> {
4845 file.seek(SeekFrom::Start(offset))?;
4846 let mut remaining = size;
4847 let mut checksum = 1_u32;
4848 let mut buffer = vec![0_u8; TABLE_CHECKSUM_BUFFER_SIZE];
4849 while remaining > 0 {
4850 let take = usize::try_from(remaining.min(TABLE_CHECKSUM_BUFFER_SIZE as u64))
4851 .expect("table checksum read is bounded by the buffer");
4852 file.read_exact(&mut buffer[..take])?;
4853 checksum = adler32_update(checksum, &buffer[..take]);
4854 remaining -= u64::try_from(take).expect("usize fits u64");
4855 }
4856 Ok(checksum)
4857}
4858
4859fn validate_present_ewf1_media_checksum(data: &[u8], section_type: &str) -> Result<()> {
4860 let Some(checksum_offset) = data.len().checked_sub(4) else {
4861 return Ok(());
4862 };
4863 validate_present_adler32_checksum(
4864 data,
4865 checksum_offset,
4866 checksum_offset,
4867 &format!("EWF1 {section_type}"),
4868 )
4869}
4870
4871fn validate_adler32_checksum_value(stored: u32, checksum_data: &[u8], label: &str) -> Result<()> {
4872 let calculated = adler32(checksum_data);
4873 if stored != calculated {
4874 return Err(EwfError::Malformed(format!("{label} checksum mismatch")));
4875 }
4876 Ok(())
4877}
4878
4879fn validate_raw_chunk_checksum(encoded: &[u8], logical_size: usize) -> Result<()> {
4880 let checksum_offset = logical_size;
4881 if encoded.len() < checksum_offset + 4 {
4882 return Err(EwfError::Malformed(
4883 "raw chunk checksum trailer is missing".into(),
4884 ));
4885 }
4886 validate_adler32_checksum(encoded, checksum_offset, logical_size, "raw chunk")
4887}
4888
4889fn adler32(data: &[u8]) -> u32 {
4890 adler32_update(1, data)
4891}
4892
4893fn adler32_update(checksum: u32, data: &[u8]) -> u32 {
4894 const MOD_ADLER: u32 = 65_521;
4895 let mut a = checksum & 0xffff;
4896 let mut b = checksum >> 16;
4897 for byte in data {
4898 a = (a + u32::from(*byte)) % MOD_ADLER;
4899 b = (b + a) % MOD_ADLER;
4900 }
4901 (b << 16) | a
4902}
4903
4904fn insert_hash_value(
4905 stored_hashes: &mut StoredHashes,
4906 identifier: &str,
4907 hash: &[u8],
4908) -> Result<()> {
4909 crate::hashes::insert_stored_hash(stored_hashes, identifier, &hex_string(hash))
4910}
4911
4912fn hex_string(bytes: &[u8]) -> String {
4913 const HEX: &[u8; 16] = b"0123456789abcdef";
4914 let mut out = String::with_capacity(bytes.len() * 2);
4915 for byte in bytes {
4916 out.push(char::from(HEX[(byte >> 4) as usize]));
4917 out.push(char::from(HEX[(byte & 0x0f) as usize]));
4918 }
4919 out
4920}
4921
4922fn validate_hash_section_size(observed: u64, expected: u64, label: &str) -> Result<()> {
4923 if observed != expected {
4924 return Err(EwfError::Malformed(format!(
4925 "{label} hash section has size {observed}, expected {expected}"
4926 )));
4927 }
4928 Ok(())
4929}
4930
4931fn validate_set_identifier(
4932 expected: &mut Option<[u8; 16]>,
4933 observed: Option<[u8; 16]>,
4934) -> Result<()> {
4935 let Some(observed) = observed else {
4936 return Ok(());
4937 };
4938 if let Some(expected) = expected {
4939 if *expected != observed {
4940 return Err(EwfError::Malformed(
4941 "segment set identifier mismatch".into(),
4942 ));
4943 }
4944 } else {
4945 *expected = Some(observed);
4946 }
4947 Ok(())
4948}
4949
4950fn validate_ewf2_header_profile(
4951 expected: &mut Option<Ewf2HeaderProfile>,
4952 observed: Option<Ewf2HeaderProfile>,
4953) -> Result<()> {
4954 let Some(observed) = observed else {
4955 return Ok(());
4956 };
4957 if let Some(expected) = expected {
4958 if expected.major_version != observed.major_version
4959 || expected.minor_version != observed.minor_version
4960 {
4961 return Err(EwfError::Malformed(
4962 "EWF2 segment file format version mismatch".into(),
4963 ));
4964 }
4965 if expected.compression_method != observed.compression_method {
4966 return Err(EwfError::Malformed(
4967 "EWF2 segment file compression method mismatch".into(),
4968 ));
4969 }
4970 } else {
4971 *expected = Some(observed);
4972 }
4973 Ok(())
4974}
4975
4976fn ewf1_metadata_payload(data: &[u8], compression_method: CompressionMethod) -> Result<Vec<u8>> {
4977 if compression_method == CompressionMethod::Zstd && data.starts_with(&[0x28, 0xb5, 0x2f, 0xfd])
4978 {
4979 let decoder = ruzstd::decoding::StreamingDecoder::new_with_max_window_size(
4980 data,
4981 MAX_DECOMPRESSED_METADATA,
4982 )
4983 .map_err(|err| {
4984 EwfError::Malformed(format!(
4985 "EWF1 metadata Zstandard decoder initialization failed: {err}"
4986 ))
4987 })?;
4988 return decompress_metadata(decoder, "EWF1");
4989 }
4990
4991 let mut decompressed = Vec::new();
4992 let result = flate2::read::ZlibDecoder::new(data)
4993 .take(MAX_DECOMPRESSED_METADATA + 1)
4994 .read_to_end(&mut decompressed);
4995 if result.is_ok() && decompressed.len() <= MAX_DECOMPRESSED_METADATA as usize {
4996 Ok(decompressed)
4997 } else {
4998 Ok(data.to_vec())
4999 }
5000}
5001
5002fn ewf2_metadata_payload(
5003 data: &[u8],
5004 _compression_method: ewf2::CompressionMethod,
5005) -> Result<Vec<u8>> {
5006 if data.first() == Some(&0x78) {
5007 return decompress_metadata(flate2::read::ZlibDecoder::new(data), "EWF2");
5008 }
5009 if data.starts_with(b"BZh") {
5010 return decompress_metadata(bzip2::read::BzDecoder::new(data), "EWF2");
5011 }
5012 Ok(data.to_vec())
5013}
5014
5015fn decode_ewf2_string_section(
5016 data: &[u8],
5017 compression_method: ewf2::CompressionMethod,
5018 label: &str,
5019) -> Result<String> {
5020 let payload = ewf2_metadata_payload(data, compression_method)?;
5021 if payload.len() % 2 != 0 {
5022 return Err(EwfError::Malformed(format!(
5023 "EWF2 {label} section has odd UTF-16 size"
5024 )));
5025 }
5026
5027 let units: Vec<u16> = payload
5028 .as_chunks::<2>()
5029 .0
5030 .iter()
5031 .map(|chunk| u16::from_le_bytes(*chunk))
5032 .collect();
5033 let text = String::from_utf16(&units)
5034 .map_err(|_| EwfError::Malformed(format!("EWF2 {label} section is not valid UTF-16LE")))?;
5035 Ok(text.strip_prefix('\u{feff}').unwrap_or(&text).to_owned())
5036}
5037
5038fn decompress_metadata(reader: impl Read, format: &str) -> Result<Vec<u8>> {
5039 let mut decompressed = Vec::new();
5040 reader
5041 .take(MAX_DECOMPRESSED_METADATA + 1)
5042 .read_to_end(&mut decompressed)
5043 .map_err(|err| {
5044 EwfError::Malformed(format!("{format} metadata decompression failed: {err}"))
5045 })?;
5046 if decompressed.len() > MAX_DECOMPRESSED_METADATA as usize {
5047 return Err(EwfError::Malformed(format!(
5048 "{format} metadata exceeds decompressed size limit"
5049 )));
5050 }
5051 Ok(decompressed)
5052}
5053
5054#[cfg(test)]
5055mod tests {
5056 use std::io::{Cursor, Read, Seek, SeekFrom, Write};
5057 use std::sync::{
5058 Arc,
5059 atomic::{AtomicUsize, Ordering as AtomicOrdering},
5060 };
5061
5062 use flate2::write::ZlibEncoder;
5063
5064 use super::*;
5065
5066 struct ObservedReader {
5067 cursor: Cursor<Vec<u8>>,
5068 maximum_read: usize,
5069 }
5070
5071 impl Read for ObservedReader {
5072 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
5073 self.maximum_read = self.maximum_read.max(buffer.len());
5074 self.cursor.read(buffer)
5075 }
5076 }
5077
5078 impl Seek for ObservedReader {
5079 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
5080 self.cursor.seek(position)
5081 }
5082 }
5083
5084 struct LengthObservedReader {
5085 cursor: Cursor<Vec<u8>>,
5086 seek_from_end_calls: Arc<AtomicUsize>,
5087 }
5088
5089 impl Read for LengthObservedReader {
5090 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
5091 self.cursor.read(buffer)
5092 }
5093 }
5094
5095 impl Seek for LengthObservedReader {
5096 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
5097 if matches!(position, SeekFrom::End(_)) {
5098 self.seek_from_end_calls
5099 .fetch_add(1, AtomicOrdering::Relaxed);
5100 }
5101 self.cursor.seek(position)
5102 }
5103 }
5104
5105 #[test]
5106 fn segment_file_pool_caches_segment_lengths() {
5107 let seek_from_end_calls = Arc::new(AtomicUsize::new(0));
5108 let reader = LengthObservedReader {
5109 cursor: Cursor::new(vec![0; 128]),
5110 seek_from_end_calls: Arc::clone(&seek_from_end_calls),
5111 };
5112 let mut pool = SegmentFilePool::new_readers(
5113 vec![Box::new(reader)],
5114 None,
5115 Arc::new(ReaderStatisticsCollector::new(false)),
5116 )
5117 .unwrap();
5118 let path = Path::new("cached-length.E01");
5119
5120 assert_eq!(pool.segment_len(0, path).unwrap(), 128);
5121 assert_eq!(pool.segment_len(0, path).unwrap(), 128);
5122 assert_eq!(seek_from_end_calls.load(AtomicOrdering::Relaxed), 1);
5123 }
5124
5125 #[test]
5126 fn active_positioned_reads_preserve_the_handle_limit() {
5127 let first = tempfile::NamedTempFile::new().unwrap();
5128 let second = tempfile::NamedTempFile::new().unwrap();
5129 let mut pool =
5130 SegmentFilePool::new_path(2, Some(1), Arc::new(ReaderStatisticsCollector::new(false)))
5131 .unwrap();
5132
5133 let active = pool.acquire_path_file(0, first.path()).unwrap().unwrap();
5134 assert_eq!(pool.open_count(), 1);
5135 assert!(pool.acquire_path_file(1, second.path()).unwrap().is_none());
5136 assert_eq!(pool.open_count(), 1);
5137
5138 drop(active);
5139 assert!(pool.acquire_path_file(1, second.path()).unwrap().is_some());
5140 assert_eq!(pool.open_count(), 1);
5141 }
5142
5143 #[test]
5144 fn table_checksum_validation_uses_bounded_reads() {
5145 const MAXIMUM_CHECKSUM_READ: usize = 64 * 1024;
5146 let entries = vec![0x5a; MAXIMUM_CHECKSUM_READ * 3 + 17];
5147 let mut bytes = entries.clone();
5148 bytes.extend_from_slice(&adler32(&entries).to_le_bytes());
5149 let mut reader = ObservedReader {
5150 cursor: Cursor::new(bytes),
5151 maximum_read: 0,
5152 };
5153
5154 validate_present_table_entries_checksum(
5155 &mut reader,
5156 0,
5157 entries.len() as u64,
5158 entries.len() as u64,
5159 "test table",
5160 &ReaderStatisticsCollector::new(false),
5161 )
5162 .unwrap();
5163
5164 assert!(reader.maximum_read <= MAXIMUM_CHECKSUM_READ);
5165 }
5166
5167 #[test]
5168 fn table_checksum_validation_rejects_mismatch_across_multiple_reads() {
5169 const CHECKSUM_READ_SIZE: usize = 64 * 1024;
5170 let entries = vec![0x3c; CHECKSUM_READ_SIZE * 2 + 7];
5171 let mut bytes = entries.clone();
5172 bytes.extend_from_slice(&adler32(b"different").to_le_bytes());
5173 let mut reader = Cursor::new(bytes);
5174
5175 let error = validate_present_table_entries_checksum(
5176 &mut reader,
5177 0,
5178 entries.len() as u64,
5179 entries.len() as u64,
5180 "test table",
5181 &ReaderStatisticsCollector::new(false),
5182 )
5183 .unwrap_err();
5184
5185 match error {
5186 EwfError::Malformed(message) => {
5187 assert_eq!(message, "test table checksum mismatch");
5188 }
5189 other => panic!("unexpected error: {other}"),
5190 }
5191 }
5192
5193 #[test]
5194 fn ewf1_metadata_payload_decompresses_zlib_data() {
5195 let mut encoder = ZlibEncoder::new(Vec::new(), flate2::Compression::default());
5196 encoder.write_all(b"metadata").unwrap();
5197 let compressed = encoder.finish().unwrap();
5198
5199 assert_eq!(
5200 ewf1_metadata_payload(&compressed, CompressionMethod::Zlib).unwrap(),
5201 b"metadata"
5202 );
5203 }
5204
5205 #[test]
5206 fn ewf1_metadata_payload_keeps_plain_data() {
5207 assert_eq!(
5208 ewf1_metadata_payload(b"plain metadata", CompressionMethod::Zlib).unwrap(),
5209 b"plain metadata"
5210 );
5211 }
5212
5213 #[test]
5214 fn ewf2_metadata_payload_decompresses_zlib_data() {
5215 let mut encoder = ZlibEncoder::new(Vec::new(), flate2::Compression::default());
5216 encoder.write_all(b"metadata").unwrap();
5217 let compressed = encoder.finish().unwrap();
5218
5219 assert_eq!(
5220 ewf2_metadata_payload(&compressed, ewf2::CompressionMethod::Zlib).unwrap(),
5221 b"metadata"
5222 );
5223 }
5224}