Skip to main content

ewf_image/
image.rs

1use std::collections::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, Mutex,
8    atomic::{AtomicBool, Ordering},
9};
10use std::time::Instant;
11
12use lru::LruCache;
13use md5::Digest as _;
14
15use crate::codepage::decode_header_bytes;
16use crate::decode::{
17    ChunkEncoding, decode_chunk, raw_chunk_size_cap, validate_encoded_size,
18    zlib_compressed_chunk_size_cap,
19};
20use crate::format::{ewf1, ewf2};
21use crate::index::{LazyChunkIndex, TableRange, TableRangeKind};
22use crate::metadata::{
23    Ewf2Geometry, detect_ewf1_header_profile, detect_ewf1_header2_profile, parse_error2_data,
24    parse_ewf2_case_data, parse_ewf2_device_info, parse_ewf2_device_info_values,
25    parse_ewf2_error_table_data, parse_header_data, parse_header2_data, parse_session_data,
26    parse_xhash_data, parse_xheader_data,
27};
28use crate::reader_cache::{TABLE_PAGE_SIZE, TablePageCache, TablePageKey};
29use crate::reader_statistics::{ReaderCacheInfo, ReaderStatistics, ReaderStatisticsCollector};
30use crate::segment::discover_segments;
31use crate::signature::{check_segment_files_corruption, check_segment_files_encryption};
32use crate::single_files::parse_ewf2_single_files_data;
33use crate::types::{
34    AcquisitionError, ChunkCacheCapacity, CompressionLevel, CompressionMethod, CompressionValues,
35    DataChunk, DataChunkEncoding, EncodedDataChunk, EwfMetadata, Format, FormatProfile,
36    HeaderCodepage, HeaderDateFormat, ImageInfo, MediaFlags, MediaInfo, MediaType, MemoryExtent,
37    OpenOptions, OpenStrictness, SectorRange, SegmentFileVersion, SingleFileEntry,
38    SingleFilePermission, SingleFileSource, SingleFileSubject, SingleFilesAuxTables,
39    SingleFilesInfo, StoredHashes,
40};
41use crate::{EwfError, Result};
42
43const MAX_DECOMPRESSED_METADATA: u64 = 16 * 1024 * 1024;
44const MAX_CHUNK_SIZE: u64 = 128 * 1024 * 1024;
45const EWF1_HASH_SECTION_SIZE: u64 = 36;
46const EWF1_DIGEST_SECTION_SIZE: u64 = 80;
47const EWF1_LTREE_HEADER_SIZE: usize = 48;
48const EWF2_HASH_SECTION_SIZE: u64 = 32;
49const EWF2_TABLE_HEADER_V2_SIZE: u64 = 32;
50const EWF2_TABLE_FOOTER_SIZE: u64 = 16;
51const TABLE_CHECKSUM_BUFFER_SIZE: usize = 64 * 1024;
52
53/// Reader type accepted by [`Image::open_readers`].
54///
55/// Implemented automatically for any `Read + Seek + Send` type. Override
56/// [`SegmentReader::segment_len`] when obtaining the length by seeking to the
57/// end is not appropriate.
58pub trait SegmentReader: Read + Seek + Send {
59    /// Returns the total length of this segment in bytes.
60    fn segment_len(&mut self) -> io::Result<u64> {
61        let position = self.stream_position()?;
62        let len = self.seek(SeekFrom::End(0))?;
63        self.seek(SeekFrom::Start(position))?;
64        Ok(len)
65    }
66}
67
68impl<T> SegmentReader for T where T: Read + Seek + Send {}
69
70type SegmentReaderHandle = Box<dyn SegmentReader>;
71
72#[derive(Debug, Clone)]
73/// Opened EWF image and logical media reader.
74pub struct Image {
75    inner: Arc<ImageInner>,
76}
77
78#[derive(Debug)]
79struct ImageInner {
80    info: ImageInfo,
81    segments: Mutex<SegmentFilePool>,
82    index: LazyChunkIndex,
83    chunk_cache: Mutex<LruCache<u64, Arc<Vec<u8>>>>,
84    chunk_cache_capacity_bytes: u64,
85    table_page_cache: Mutex<TablePageCache>,
86    statistics: Arc<ReaderStatisticsCollector>,
87    checksum_errors: Mutex<Vec<SectorRange>>,
88    read_zero_chunk_on_error: AtomicBool,
89    abort_signaled: AtomicBool,
90}
91
92struct SegmentFilePool {
93    files: Vec<Option<SegmentReaderHandle>>,
94    lengths: Vec<Option<u64>>,
95    ever_opened: Vec<bool>,
96    open_order: VecDeque<usize>,
97    maximum_open_handles: Option<usize>,
98    mode: SegmentFilePoolMode,
99    statistics: Arc<ReaderStatisticsCollector>,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103enum SegmentFilePoolMode {
104    ReopenFromPath,
105    SuppliedReaders,
106}
107
108impl std::fmt::Debug for SegmentFilePool {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        f.debug_struct("SegmentFilePool")
111            .field("segment_count", &self.files.len())
112            .field(
113                "known_length_count",
114                &self
115                    .lengths
116                    .iter()
117                    .filter(|length| length.is_some())
118                    .count(),
119            )
120            .field(
121                "ever_opened_count",
122                &self.ever_opened.iter().filter(|opened| **opened).count(),
123            )
124            .field("open_count", &self.open_count())
125            .field("open_order", &self.open_order)
126            .field("maximum_open_handles", &self.maximum_open_handles)
127            .field("mode", &self.mode)
128            .field("statistics_enabled", &self.statistics.enabled())
129            .finish()
130    }
131}
132
133#[derive(Debug, Clone, Copy)]
134struct Chunk {
135    segment_index: usize,
136    offset: u64,
137    encoded_size: u64,
138    logical_size: usize,
139    encoding: ChunkEncoding,
140    validate_checksum: bool,
141}
142
143#[derive(Clone, Copy)]
144struct Ewf1DecodedEntry {
145    compressed: bool,
146    offset: u64,
147}
148
149#[derive(Debug, Clone)]
150/// Seekable cursor over an [`Image`] logical media stream.
151pub struct ImageCursor {
152    image: Image,
153    position: u64,
154}
155
156#[derive(Debug, Clone)]
157/// Seekable cursor over one logical single-file catalog entry.
158pub struct SingleFileCursor {
159    image: Image,
160    entry: SingleFileEntry,
161    position: u64,
162}
163
164impl Image {
165    /// Opens an EWF image from the first segment path.
166    ///
167    /// Adjacent segments are discovered automatically from the first path.
168    ///
169    /// # Errors
170    ///
171    /// Returns an error if no segment set can be discovered, a segment cannot
172    /// be read, or the image is unsupported or malformed.
173    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
174        Self::open_with_options(path, OpenOptions::default())
175    }
176
177    /// Opens an EWF image from the first segment path with explicit options.
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if no segment set can be discovered, a segment cannot
182    /// be read, or the image is unsupported or malformed.
183    pub fn open_with_options(path: impl AsRef<Path>, options: OpenOptions) -> Result<Self> {
184        let paths = discover_segments(path.as_ref())?;
185        Self::open_segment_paths(paths, options)
186    }
187
188    /// Opens an EWF image from an explicit ordered segment path list.
189    ///
190    /// # Errors
191    ///
192    /// Returns an error if the list is empty, a segment cannot be read, or the
193    /// image is unsupported or malformed.
194    pub fn open_segments<P, I>(paths: I) -> Result<Self>
195    where
196        P: AsRef<Path>,
197        I: IntoIterator<Item = P>,
198    {
199        Self::open_segments_with_options(paths, OpenOptions::default())
200    }
201
202    /// Opens an EWF image from explicit ordered segment paths with options.
203    ///
204    /// # Errors
205    ///
206    /// Returns an error if the list is empty, a segment cannot be read, or the
207    /// image is unsupported or malformed.
208    pub fn open_segments_with_options<P, I>(paths: I, options: OpenOptions) -> Result<Self>
209    where
210        P: AsRef<Path>,
211        I: IntoIterator<Item = P>,
212    {
213        let paths = paths
214            .into_iter()
215            .map(|path| path.as_ref().to_path_buf())
216            .collect();
217        Self::open_segment_paths(paths, options)
218    }
219
220    /// Opens an EWF image from supplied readers and segment labels.
221    ///
222    /// Supplied readers are kept by the image and are not reopened from the
223    /// filesystem. Labels are used anywhere this crate reports segment paths.
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if the list is empty, a reader fails, or the image is
228    /// unsupported or malformed.
229    pub fn open_readers<N, R, I>(segments: I) -> Result<Self>
230    where
231        N: Into<PathBuf>,
232        R: SegmentReader + 'static,
233        I: IntoIterator<Item = (N, R)>,
234    {
235        Self::open_readers_with_options(segments, OpenOptions::default())
236    }
237
238    /// Opens an EWF image from supplied readers with explicit options.
239    ///
240    /// # Errors
241    ///
242    /// Returns an error if the list is empty, a reader fails, or the image is
243    /// unsupported or malformed.
244    pub fn open_readers_with_options<N, R, I>(segments: I, options: OpenOptions) -> Result<Self>
245    where
246        N: Into<PathBuf>,
247        R: SegmentReader + 'static,
248        I: IntoIterator<Item = (N, R)>,
249    {
250        let mut paths = Vec::new();
251        let mut readers = Vec::new();
252        for (name, reader) in segments {
253            paths.push(name.into());
254            readers.push(Box::new(reader) as SegmentReaderHandle);
255        }
256        Self::open_segment_readers(paths, readers, options)
257    }
258
259    fn open_segment_paths(paths: Vec<PathBuf>, options: OpenOptions) -> Result<Self> {
260        if paths.is_empty() {
261            return Err(EwfError::NoSegments("empty segment list".into()));
262        }
263
264        let statistics = Arc::new(ReaderStatisticsCollector::new(
265            options.reader_statistics_enabled(),
266        ));
267        let segments =
268            SegmentFilePool::new_path(paths.len(), options.maximum_open_handles(), statistics)?;
269        Self::open_segment_sources(paths, segments, options)
270    }
271
272    fn open_segment_readers(
273        paths: Vec<PathBuf>,
274        readers: Vec<SegmentReaderHandle>,
275        options: OpenOptions,
276    ) -> Result<Self> {
277        if paths.is_empty() {
278            return Err(EwfError::NoSegments("empty segment list".into()));
279        }
280        if paths.len() != readers.len() {
281            return Err(EwfError::Malformed(
282                "segment reader count does not match segment labels".into(),
283            ));
284        }
285
286        let statistics = Arc::new(ReaderStatisticsCollector::new(
287            options.reader_statistics_enabled(),
288        ));
289        let segments =
290            SegmentFilePool::new_readers(readers, options.maximum_open_handles(), statistics)?;
291        Self::open_segment_sources(paths, segments, options)
292    }
293
294    fn open_segment_sources(
295        paths: Vec<PathBuf>,
296        mut segments: SegmentFilePool,
297        options: OpenOptions,
298    ) -> Result<Self> {
299        let statistics = Arc::clone(&segments.statistics);
300        let mut ranges = Vec::new();
301        let mut metadata = EwfMetadata::default();
302        let mut acquisition_errors = Vec::new();
303        let mut memory_extents = Vec::new();
304        let mut single_files = None;
305        let mut ewf2_single_files_tables = SingleFilesAuxTables::default();
306        let mut ewf2_increment_data = Vec::new();
307        let mut ewf2_final_information = None;
308        let mut ewf2_restart_data = None;
309        let mut ewf2_analytical_data = None;
310        let mut sessions = Vec::new();
311        let mut tracks = Vec::new();
312        let mut stored_hashes = StoredHashes::default();
313        let mut media = MediaInfo::default();
314        let mut chunk_size = 0;
315        let mut logical_size = 0;
316        let mut acquisition_complete = true;
317        let mut format = None;
318        let mut format_profile = None;
319        let mut format_profile_hint_only = false;
320        let mut next_ewf1_chunk = 0_u64;
321        let mut discovered_table_chunks = 0_u64;
322        let mut expected_set_identifier: Option<[u8; 16]> = None;
323        let mut expected_ewf2_header_profile = None;
324        let mut expected_ewf2_device_information = None;
325        let mut expected_ewf2_case_data = None;
326
327        for (segment_index, path) in paths.iter().enumerate() {
328            statistics.record_segment_parse();
329            let parsed = {
330                let file = segments.file_mut(segment_index, path)?;
331                parse_segment(
332                    file.as_mut(),
333                    path,
334                    segment_index,
335                    next_ewf1_chunk,
336                    options.strictness(),
337                    options.header_codepage(),
338                    &statistics,
339                )?
340            };
341            let expected_segment_number = u64::try_from(segment_index + 1)
342                .map_err(|_| EwfError::Malformed("segment index overflow".into()))?;
343            if parsed.segment_number != expected_segment_number {
344                return Err(EwfError::Malformed(format!(
345                    "segment {} declares segment number {}, expected {}",
346                    segment_index + 1,
347                    parsed.segment_number,
348                    expected_segment_number
349                )));
350            }
351            validate_set_identifier(&mut expected_set_identifier, parsed.set_identifier)?;
352            validate_ewf2_header_profile(
353                &mut expected_ewf2_header_profile,
354                parsed.ewf2_header_profile,
355            )?;
356            if let Some(device_information) = parsed.ewf2_device_information.as_deref() {
357                remember_ewf2_metadata_payload(
358                    &mut expected_ewf2_device_information,
359                    device_information,
360                    "device information",
361                )?;
362            }
363            if let Some(case_data) = parsed.ewf2_case_data.as_deref() {
364                remember_ewf2_metadata_payload(
365                    &mut expected_ewf2_case_data,
366                    case_data,
367                    "case data",
368                )?;
369            }
370            if segment_index == 0 {
371                chunk_size = parsed.chunk_size;
372                logical_size = parsed.logical_size;
373                acquisition_complete = parsed.acquisition_complete;
374                media = parsed.media;
375                metadata = parsed.metadata;
376                acquisition_errors = parsed.acquisition_errors;
377                memory_extents = parsed.memory_extents;
378                single_files = parsed.single_files;
379                ewf2_single_files_tables = parsed.ewf2_single_files_tables;
380                ewf2_increment_data = parsed.ewf2_increment_data;
381                ewf2_final_information = parsed.ewf2_final_information;
382                ewf2_restart_data = parsed.ewf2_restart_data;
383                ewf2_analytical_data = parsed.ewf2_analytical_data;
384                sessions = parsed.sessions;
385                tracks = parsed.tracks;
386                format = Some(parsed.format);
387                format_profile = Some(parsed.format_profile);
388                format_profile_hint_only = parsed.format_profile_hint_only;
389            } else {
390                merge_segment_format_profile(
391                    &mut format_profile,
392                    &mut format_profile_hint_only,
393                    parsed.format_profile,
394                    parsed.format_profile_hint_only,
395                )?;
396                memory_extents.extend(parsed.memory_extents);
397                merge_single_files(&mut single_files, parsed.single_files)?;
398                merge_single_files_aux_tables(
399                    &mut ewf2_single_files_tables,
400                    parsed.ewf2_single_files_tables,
401                )?;
402                ewf2_increment_data.extend(parsed.ewf2_increment_data);
403                merge_optional_ewf2_raw_section(
404                    &mut ewf2_final_information,
405                    parsed.ewf2_final_information,
406                    "final information",
407                )?;
408                merge_optional_ewf2_string_section(
409                    &mut ewf2_restart_data,
410                    parsed.ewf2_restart_data,
411                    "restart data",
412                )?;
413                merge_optional_ewf2_string_section(
414                    &mut ewf2_analytical_data,
415                    parsed.ewf2_analytical_data,
416                    "analytical data",
417                )?;
418                sessions.extend(parsed.sessions);
419                tracks.extend(parsed.tracks);
420                acquisition_complete = parsed.acquisition_complete;
421            }
422            merge_hashes(&mut stored_hashes, &parsed.stored_hashes);
423            if parsed.format == Format::Ewf1 {
424                next_ewf1_chunk = next_ewf1_chunk
425                    .checked_add(parsed.table_chunk_count)
426                    .ok_or_else(|| EwfError::Malformed("EWF1 chunk count overflow".into()))?;
427            }
428            for range in &parsed.ranges {
429                discovered_table_chunks = discovered_table_chunks.max(
430                    range
431                        .first_chunk
432                        .checked_add(range.chunk_count)
433                        .ok_or_else(|| {
434                            EwfError::Malformed("table range chunk count overflow".into())
435                        })?,
436                );
437            }
438            ranges.extend(parsed.ranges);
439        }
440
441        let format = format.ok_or_else(|| EwfError::Malformed("image has no segments".into()))?;
442        if format == Format::Ewf2
443            && expected_ewf2_device_information.is_none()
444            && expected_ewf2_case_data.is_none()
445        {
446            return Err(EwfError::Malformed(
447                "missing EWF2 device information or case data section".into(),
448            ));
449        }
450
451        if logical_size == 0 && chunk_size > 0 {
452            logical_size = chunk_size
453                .checked_mul(discovered_table_chunks)
454                .ok_or_else(|| EwfError::Malformed("logical size overflow".into()))?;
455        }
456        if discovered_table_chunks > 0 {
457            media.chunk_count = Some(discovered_table_chunks);
458        }
459
460        let info = ImageInfo {
461            format,
462            format_profile: format_profile.unwrap_or_default(),
463            segment_count: paths.len(),
464            segment_paths: paths,
465            chunk_size,
466            logical_size,
467            acquisition_complete,
468            header_codepage: options.header_codepage(),
469            header_values_date_format: options.header_values_date_format(),
470            media,
471            metadata,
472            stored_hashes,
473            acquisition_errors,
474            memory_extents,
475            single_files,
476            ewf2_single_files_tables,
477            ewf2_increment_data,
478            ewf2_final_information,
479            ewf2_restart_data,
480            ewf2_analytical_data,
481            sessions,
482            tracks,
483        };
484        let index = LazyChunkIndex::new(ranges, info.logical_size, info.chunk_size)?;
485        let chunk_size = usize::try_from(info.chunk_size)
486            .map_err(|_| EwfError::Malformed("chunk size does not fit usize".into()))?;
487        let cache_entries = match options.chunk_cache_capacity() {
488            ChunkCacheCapacity::Chunks(entries) => entries.max(1),
489            ChunkCacheCapacity::Bytes(bytes) => bytes.checked_div(chunk_size).unwrap_or(0).max(1),
490        };
491        let cache_size =
492            NonZeroUsize::new(cache_entries).expect("chunk cache size is at least one");
493        let chunk_cache_capacity_bytes = u64::try_from(cache_entries)
494            .unwrap_or(u64::MAX)
495            .saturating_mul(info.chunk_size);
496
497        Ok(Self {
498            inner: Arc::new(ImageInner {
499                info,
500                segments: Mutex::new(segments),
501                index,
502                chunk_cache: Mutex::new(LruCache::new(cache_size)),
503                chunk_cache_capacity_bytes,
504                table_page_cache: Mutex::new(TablePageCache::new(
505                    options.table_entry_cache_size_bytes(),
506                )),
507                statistics,
508                checksum_errors: Mutex::new(Vec::new()),
509                read_zero_chunk_on_error: AtomicBool::new(options.read_zero_chunk_on_error()),
510                abort_signaled: AtomicBool::new(false),
511            }),
512        })
513    }
514
515    /// Returns parsed image metadata and geometry.
516    pub fn info(&self) -> &ImageInfo {
517        &self.inner.info
518    }
519
520    /// Returns cumulative reader counters when statistics were enabled at open time.
521    ///
522    /// Snapshots are shared across clones and cursors created from this image.
523    pub fn reader_statistics(&self) -> Option<ReaderStatistics> {
524        self.inner.statistics.snapshot()
525    }
526
527    /// Returns configured capacities and retained table-page payload bytes.
528    pub fn reader_cache_info(&self) -> ReaderCacheInfo {
529        let cache = self
530            .inner
531            .table_page_cache
532            .lock()
533            .unwrap_or_else(std::sync::PoisonError::into_inner);
534        ReaderCacheInfo::new(
535            self.inner.chunk_cache_capacity_bytes,
536            cache.capacity_bytes(),
537            cache.cached_bytes(),
538            cache.peak_bytes(),
539        )
540    }
541
542    /// Returns the first segment filename or supplied-reader label.
543    pub fn filename(&self) -> &Path {
544        self.inner.info.segment_paths[0].as_path()
545    }
546
547    /// Returns the number of segments in the opened image.
548    pub fn number_of_segments(&self) -> usize {
549        self.inner.info.segment_count
550    }
551
552    /// Returns the configured maximum number of open segment handles.
553    ///
554    /// # Errors
555    ///
556    /// Returns an error if the internal segment pool lock is poisoned.
557    pub fn maximum_number_of_open_handles(&self) -> Result<Option<usize>> {
558        Ok(self
559            .inner
560            .segments
561            .lock()
562            .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
563            .maximum_open_handles())
564    }
565
566    /// Updates the maximum number of open segment handles.
567    ///
568    /// `None` allows all handles to remain open. Supplied-reader images cannot
569    /// evict and reopen readers, so this value is validated against the reader
570    /// set.
571    ///
572    /// # Errors
573    ///
574    /// Returns an error if the value is invalid for supplied readers or if the
575    /// internal segment pool lock is poisoned.
576    pub fn set_maximum_number_of_open_handles(
577        &self,
578        maximum_open_handles: Option<usize>,
579    ) -> Result<()> {
580        self.inner
581            .segments
582            .lock()
583            .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
584            .set_maximum_open_handles(maximum_open_handles)
585    }
586
587    /// Returns the current number of open segment handles.
588    ///
589    /// # Errors
590    ///
591    /// Returns an error if the internal segment pool lock is poisoned.
592    pub fn number_of_open_segment_handles(&self) -> Result<usize> {
593        Ok(self
594            .inner
595            .segments
596            .lock()
597            .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
598            .open_count())
599    }
600
601    /// Returns all segment filenames or supplied-reader labels.
602    pub fn segment_filenames(&self) -> &[PathBuf] {
603        &self.inner.info.segment_paths
604    }
605
606    /// Returns one segment filename or supplied-reader label by index.
607    pub fn segment_filename(&self, index: usize) -> Option<&Path> {
608        self.inner
609            .info
610            .segment_paths
611            .get(index)
612            .map(PathBuf::as_path)
613    }
614
615    /// Returns the total byte size of all opened segment containers.
616    ///
617    /// For supplied readers, this uses [`SegmentReader::segment_len`].
618    ///
619    /// # Errors
620    ///
621    /// Returns an error if a segment length cannot be read, the internal segment
622    /// pool lock is poisoned, or the total size overflows `u64`.
623    pub fn segment_set_size(&self) -> Result<u64> {
624        let paths = self.inner.info.segment_paths.clone();
625        let mut segments = self
626            .inner
627            .segments
628            .lock()
629            .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?;
630        let mut size = 0_u64;
631        for (segment_index, path) in paths.iter().enumerate() {
632            size = size
633                .checked_add(segments.file_mut(segment_index, path)?.segment_len()?)
634                .ok_or_else(|| EwfError::Malformed("segment set size overflow".into()))?;
635        }
636        Ok(size)
637    }
638
639    /// Probes path-backed segment files for corruption-style structural errors.
640    ///
641    /// Images opened from supplied readers return `false` because there are no
642    /// filesystem paths to reprobe.
643    ///
644    /// # Errors
645    ///
646    /// Returns an error if a path-backed probe fails unexpectedly.
647    pub fn segment_files_corrupted(&self) -> Result<bool> {
648        if self.has_supplied_segment_readers()? {
649            return Ok(false);
650        }
651        check_segment_files_corruption(self.segment_filenames())
652    }
653
654    /// Probes path-backed segment files for EWF2 encryption markers.
655    ///
656    /// Images opened from supplied readers return `false` because there are no
657    /// filesystem paths to reprobe.
658    ///
659    /// # Errors
660    ///
661    /// Returns an error if a path-backed probe fails unexpectedly.
662    pub fn segment_files_encrypted(&self) -> Result<bool> {
663        if self.has_supplied_segment_readers()? {
664            return Ok(false);
665        }
666        check_segment_files_encryption(self.segment_filenames())
667    }
668
669    /// Returns the segment containing a logical chunk.
670    ///
671    /// # Errors
672    ///
673    /// Returns an error if `chunk_index` is outside the parsed chunk index or if
674    /// the chunk index references a missing segment.
675    pub fn segment_filename_for_chunk(&self, chunk_index: u64) -> Result<&Path> {
676        let chunk = self.lookup_chunk(chunk_index)?;
677        self.segment_filename(chunk.segment_index)
678            .ok_or_else(|| EwfError::Malformed("chunk references missing segment".into()))
679    }
680
681    /// Returns the segment containing a logical byte offset.
682    ///
683    /// Offsets at or beyond the logical media size return `Ok(None)`.
684    ///
685    /// # Errors
686    ///
687    /// Returns an error if the chunk geometry is malformed.
688    pub fn segment_filename_for_offset(&self, offset: u64) -> Result<Option<&Path>> {
689        if offset >= self.inner.info.logical_size {
690            return Ok(None);
691        }
692        let chunk_size = self.inner.info.chunk_size;
693        if chunk_size == 0 {
694            return Err(EwfError::Malformed("chunk size is zero".into()));
695        }
696        self.segment_filename_for_chunk(offset / chunk_size)
697            .map(Some)
698    }
699
700    /// Returns the top-level EWF format generation.
701    pub fn format(&self) -> Format {
702        self.inner.info.format
703    }
704
705    /// Returns the inferred format profile.
706    pub fn format_profile(&self) -> FormatProfile {
707        self.inner.info.format_profile
708    }
709
710    /// Returns the logical chunk size in bytes.
711    pub fn chunk_size(&self) -> u64 {
712        self.inner.info.chunk_size
713    }
714
715    /// Returns the logical media size in bytes.
716    pub fn media_size(&self) -> u64 {
717        self.inner.info.logical_size
718    }
719
720    /// Returns the header codepage used for decoded EWF1 values.
721    pub fn header_codepage(&self) -> HeaderCodepage {
722        self.inner.info.header_codepage
723    }
724
725    /// Returns the date format applied to header date values.
726    pub fn header_values_date_format(&self) -> HeaderDateFormat {
727        self.inner.info.header_values_date_format
728    }
729
730    /// Returns sectors per chunk from media metadata.
731    pub fn sectors_per_chunk(&self) -> Option<u64> {
732        self.inner.info.media.sectors_per_chunk
733    }
734
735    /// Returns bytes per sector from media metadata.
736    pub fn bytes_per_sector(&self) -> Option<u64> {
737        self.inner.info.media.bytes_per_sector
738    }
739
740    /// Returns the logical sector count from media metadata.
741    pub fn number_of_sectors(&self) -> Option<u64> {
742        self.inner.info.media.sector_count
743    }
744
745    /// Returns the logical chunk count from media metadata.
746    pub fn number_of_chunks(&self) -> Option<u64> {
747        self.inner.info.media.chunk_count
748    }
749
750    /// Returns error granularity in sectors from media metadata.
751    pub fn error_granularity(&self) -> Option<u64> {
752        self.inner.info.media.error_granularity
753    }
754
755    /// Returns the segment set identifier.
756    pub fn segment_file_set_identifier(&self) -> Option<[u8; 16]> {
757        self.inner.info.media.set_identifier
758    }
759
760    /// Returns the EWF2 segment file version.
761    pub fn segment_file_version(&self) -> Option<SegmentFileVersion> {
762        self.inner.info.media.ewf2_segment_file_version
763    }
764
765    /// Returns the stored compression method metadata.
766    pub fn compression_method(&self) -> Option<CompressionMethod> {
767        self.inner.info.media.compression_method
768    }
769
770    /// Returns stored compression level and flags metadata.
771    pub fn compression_values(&self) -> CompressionValues {
772        self.inner.info.media.compression_values
773    }
774
775    /// Returns media type metadata.
776    pub fn media_type(&self) -> Option<MediaType> {
777        self.inner.info.media.media_type
778    }
779
780    /// Returns media flag metadata.
781    pub fn media_flags(&self) -> MediaFlags {
782        self.inner.info.media.media_flags
783    }
784
785    /// Returns memory acquisition extents.
786    pub fn memory_extents(&self) -> &[MemoryExtent] {
787        &self.inner.info.memory_extents
788    }
789
790    /// Returns the number of memory acquisition extents.
791    pub fn number_of_memory_extents(&self) -> usize {
792        self.inner.info.memory_extents.len()
793    }
794
795    /// Returns one memory acquisition extent by index.
796    pub fn memory_extent(&self, index: usize) -> Option<&MemoryExtent> {
797        self.inner.info.memory_extents.get(index)
798    }
799
800    /// Returns raw EWF2 increment data sections.
801    pub fn ewf2_increment_data(&self) -> &[Vec<u8>] {
802        &self.inner.info.ewf2_increment_data
803    }
804
805    /// Returns the number of EWF2 increment data sections.
806    pub fn number_of_ewf2_increment_data_sections(&self) -> usize {
807        self.inner.info.ewf2_increment_data.len()
808    }
809
810    /// Returns one raw EWF2 increment data section by index.
811    pub fn ewf2_increment_data_section(&self, index: usize) -> Option<&[u8]> {
812        self.inner
813            .info
814            .ewf2_increment_data
815            .get(index)
816            .map(Vec::as_slice)
817    }
818
819    /// Returns the raw EWF2 final information section.
820    pub fn ewf2_final_information(&self) -> Option<&[u8]> {
821        self.inner.info.ewf2_final_information.as_deref()
822    }
823
824    /// Returns EWF2 restart data text.
825    pub fn ewf2_restart_data(&self) -> Option<&str> {
826        self.inner.info.ewf2_restart_data.as_deref()
827    }
828
829    /// Returns EWF2 analytical data text.
830    pub fn ewf2_analytical_data(&self) -> Option<&str> {
831        self.inner.info.ewf2_analytical_data.as_deref()
832    }
833
834    /// Returns a header value by its EWF identifier.
835    pub fn header_value(&self, identifier: &str) -> Option<std::borrow::Cow<'_, str>> {
836        self.inner
837            .info
838            .metadata
839            .header_value_with_date_format(identifier, self.inner.info.header_values_date_format)
840    }
841
842    /// Returns the number of available header values.
843    pub fn number_of_header_values(&self) -> usize {
844        self.inner.info.metadata.number_of_header_values()
845    }
846
847    /// Returns a header value identifier by enumeration index.
848    pub fn header_value_identifier(&self, index: usize) -> Option<&str> {
849        self.inner.info.metadata.header_value_identifier(index)
850    }
851
852    /// Returns a stored hash string by identifier.
853    pub fn hash_value(&self, identifier: &str) -> Option<&str> {
854        self.inner.info.stored_hashes.hash_value(identifier)
855    }
856
857    /// Returns the number of stored hash strings.
858    pub fn number_of_hash_values(&self) -> usize {
859        self.inner.info.stored_hashes.number_of_hash_values()
860    }
861
862    /// Returns a stored hash identifier by enumeration index.
863    pub fn hash_value_identifier(&self, index: usize) -> Option<&str> {
864        self.inner.info.stored_hashes.hash_value_identifier(index)
865    }
866
867    /// Returns a seekable cursor over the logical media stream.
868    pub fn cursor(&self) -> ImageCursor {
869        self.inner.statistics.record_cursor_created();
870        ImageCursor {
871            image: self.clone(),
872            position: 0,
873        }
874    }
875
876    /// Reads logical media bytes at an absolute byte offset.
877    ///
878    /// Returns `Ok(0)` when `buf` is empty or `offset` is at or beyond the
879    /// logical media size.
880    ///
881    /// # Errors
882    ///
883    /// Returns an error if chunk lookup, decoding, checksum validation, or
884    /// segment I/O fails, or if [`Image::signal_abort`] has been called.
885    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
886        self.ensure_not_aborted()?;
887        if buf.is_empty() || offset >= self.inner.info.logical_size {
888            return Ok(0);
889        }
890
891        let chunk_size = self.inner.info.chunk_size;
892        if chunk_size == 0 {
893            return Err(EwfError::Malformed("chunk size is zero".into()));
894        }
895
896        let available = self.inner.info.logical_size - offset;
897        let requested = u64::try_from(buf.len())
898            .map_err(|_| EwfError::Malformed("read buffer length does not fit u64".into()))?;
899        let to_read = available.min(requested);
900        let mut copied = 0_usize;
901        let mut current = offset;
902
903        while u64::try_from(copied).expect("usize fits u64") < to_read {
904            self.ensure_not_aborted()?;
905            let chunk_id = current / chunk_size;
906            let page_offset = usize::try_from(current % chunk_size)
907                .map_err(|_| EwfError::Malformed("page offset does not fit usize".into()))?;
908            let decoded = self.read_chunk(chunk_id)?;
909            let remaining =
910                usize::try_from(to_read - u64::try_from(copied).unwrap()).map_err(|_| {
911                    EwfError::Malformed("remaining read size does not fit usize".into())
912                })?;
913            let page_available = decoded.len().saturating_sub(page_offset);
914            let n = remaining.min(page_available);
915            if n == 0 {
916                break;
917            }
918
919            buf[copied..copied + n].copy_from_slice(&decoded[page_offset..page_offset + n]);
920            copied += n;
921            current += u64::try_from(n).expect("usize fits u64");
922        }
923
924        Ok(copied)
925    }
926
927    /// Alias for [`Image::read_at`].
928    ///
929    /// # Errors
930    ///
931    /// Returns the same errors as [`Image::read_at`].
932    pub fn read_buffer_at_offset(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
933        self.read_at(buf, offset)
934    }
935
936    /// Reads bytes from a logical single-file catalog entry.
937    ///
938    /// Sparse extents read as zeroes. Duplicate-data entries read from their
939    /// referenced media offset.
940    ///
941    /// # Errors
942    ///
943    /// Returns an error if the entry metadata is inconsistent, the requested
944    /// file range is not covered by extents, underlying image reads fail, or
945    /// [`Image::signal_abort`] has been called.
946    pub fn read_single_file_at(
947        &self,
948        entry: &SingleFileEntry,
949        buf: &mut [u8],
950        offset: u64,
951    ) -> Result<usize> {
952        self.ensure_not_aborted()?;
953        if buf.is_empty() {
954            return Ok(0);
955        }
956
957        let file_size = single_file_size(entry)?;
958        if offset >= file_size {
959            return Ok(0);
960        }
961
962        let requested = u64::try_from(buf.len())
963            .map_err(|_| EwfError::Malformed("read buffer length does not fit u64".into()))?;
964        let to_read = requested.min(file_size - offset);
965        let mut copied = 0_usize;
966        let mut file_position = 0_u64;
967
968        if entry.extents.is_empty()
969            && let Some(duplicate_data_offset) = entry.duplicate_data_offset
970            && duplicate_data_offset >= 0
971        {
972            let duplicate_data_offset = u64::try_from(duplicate_data_offset).map_err(|_| {
973                EwfError::Malformed("single file duplicate data offset does not fit u64".into())
974            })?;
975            let image_offset = duplicate_data_offset.checked_add(offset).ok_or_else(|| {
976                EwfError::Malformed("single file duplicate data offset overflow".into())
977            })?;
978            let read_size = usize::try_from(to_read).map_err(|_| {
979                EwfError::Malformed("single file duplicate read size does not fit usize".into())
980            })?;
981            let out = &mut buf[..read_size];
982            let read = self.read_at(out, image_offset)?;
983            if read != read_size {
984                return Err(EwfError::Malformed(
985                    "single file duplicate data read was truncated".into(),
986                ));
987            }
988            return Ok(read);
989        }
990
991        for extent in &entry.extents {
992            let extent_start = file_position;
993            let extent_end = extent_start
994                .checked_add(extent.data_size)
995                .ok_or_else(|| EwfError::Malformed("single file extent range overflow".into()))?;
996            file_position = extent_end;
997
998            let read_start = offset.max(extent_start);
999            let read_end = (offset + to_read).min(extent_end);
1000            if read_start >= read_end {
1001                continue;
1002            }
1003
1004            let extent_relative_offset = read_start
1005                .checked_sub(extent_start)
1006                .ok_or_else(|| EwfError::Malformed("single file extent offset underflow".into()))?;
1007            let output_offset = usize::try_from(read_start - offset).map_err(|_| {
1008                EwfError::Malformed("single file output offset does not fit usize".into())
1009            })?;
1010            let read_size = usize::try_from(read_end - read_start).map_err(|_| {
1011                EwfError::Malformed("single file read size does not fit usize".into())
1012            })?;
1013            let out = &mut buf[output_offset..output_offset + read_size];
1014
1015            if extent.sparse {
1016                out.fill(0);
1017                copied += read_size;
1018                continue;
1019            }
1020
1021            let image_offset = extent
1022                .data_offset
1023                .checked_add(extent_relative_offset)
1024                .ok_or_else(|| {
1025                    EwfError::Malformed("single file extent data offset overflow".into())
1026                })?;
1027            let read = self.read_at(out, image_offset)?;
1028            if read != read_size {
1029                return Err(EwfError::Malformed(
1030                    "single file extent read was truncated".into(),
1031                ));
1032            }
1033            copied += read_size;
1034        }
1035
1036        if u64::try_from(copied).expect("usize fits u64") != to_read {
1037            return Err(EwfError::Malformed(
1038                "single file extents do not cover requested range".into(),
1039            ));
1040        }
1041        Ok(copied)
1042    }
1043
1044    /// Alias for [`Image::read_single_file_at`].
1045    ///
1046    /// # Errors
1047    ///
1048    /// Returns the same errors as [`Image::read_single_file_at`].
1049    pub fn read_file_entry_at(
1050        &self,
1051        entry: &SingleFileEntry,
1052        buf: &mut [u8],
1053        offset: u64,
1054    ) -> Result<usize> {
1055        self.read_single_file_at(entry, buf, offset)
1056    }
1057
1058    /// Returns the root logical single-file entry, if present.
1059    pub fn root_file_entry(&self) -> Option<&SingleFileEntry> {
1060        self.inner
1061            .info
1062            .single_files
1063            .as_ref()
1064            .map(|single_files| &single_files.root)
1065    }
1066
1067    /// Returns a logical single-file entry by path.
1068    ///
1069    /// # Errors
1070    ///
1071    /// Returns an error if `path` contains an empty entry name.
1072    pub fn file_entry_by_path(&self, path: &str) -> Result<Option<&SingleFileEntry>> {
1073        self.inner
1074            .info
1075            .single_files
1076            .as_ref()
1077            .map_or(Ok(None), |single_files| single_files.entry_by_path(path))
1078    }
1079
1080    /// Returns the source record associated with a logical single-file entry.
1081    pub fn source_for_file_entry(&self, entry: &SingleFileEntry) -> Option<&SingleFileSource> {
1082        self.inner
1083            .info
1084            .single_files
1085            .as_ref()?
1086            .source_for_entry(entry)
1087    }
1088
1089    /// Returns the subject record associated with a logical single-file entry.
1090    pub fn subject_for_file_entry(&self, entry: &SingleFileEntry) -> Option<&SingleFileSubject> {
1091        self.inner
1092            .info
1093            .single_files
1094            .as_ref()?
1095            .subject_for_entry(entry)
1096    }
1097
1098    /// Returns access-control entries associated with a logical single-file entry.
1099    pub fn access_control_entries_for_file_entry(
1100        &self,
1101        entry: &SingleFileEntry,
1102    ) -> &[SingleFilePermission] {
1103        self.inner
1104            .info
1105            .single_files
1106            .as_ref()
1107            .map_or(&[], |single_files| {
1108                single_files.access_control_entries_for_entry(entry)
1109            })
1110    }
1111
1112    /// Returns the number of access-control entries for a logical single-file entry.
1113    pub fn number_of_access_control_entries_for_file_entry(
1114        &self,
1115        entry: &SingleFileEntry,
1116    ) -> usize {
1117        self.access_control_entries_for_file_entry(entry).len()
1118    }
1119
1120    /// Returns one access-control entry for a logical single-file entry by index.
1121    pub fn access_control_entry_for_file_entry(
1122        &self,
1123        entry: &SingleFileEntry,
1124        index: usize,
1125    ) -> Option<&SingleFilePermission> {
1126        self.access_control_entries_for_file_entry(entry).get(index)
1127    }
1128
1129    /// Returns the stored MD5 hash bytes.
1130    pub fn md5_hash(&self) -> Option<[u8; 16]> {
1131        self.inner.info.stored_hashes.md5
1132    }
1133
1134    /// Returns the stored SHA1 hash bytes.
1135    pub fn sha1_hash(&self) -> Option<[u8; 20]> {
1136        self.inner.info.stored_hashes.sha1
1137    }
1138
1139    /// Returns acquisition error ranges.
1140    pub fn acquisition_errors(&self) -> &[AcquisitionError] {
1141        &self.inner.info.acquisition_errors
1142    }
1143
1144    /// Returns the number of acquisition error ranges.
1145    pub fn number_of_acquisition_errors(&self) -> usize {
1146        self.inner.info.acquisition_errors.len()
1147    }
1148
1149    /// Returns one acquisition error range by index.
1150    pub fn acquisition_error(&self, index: usize) -> Option<&AcquisitionError> {
1151        self.inner.info.acquisition_errors.get(index)
1152    }
1153
1154    /// Returns session sector ranges.
1155    pub fn sessions(&self) -> &[SectorRange] {
1156        &self.inner.info.sessions
1157    }
1158
1159    /// Returns the number of session sector ranges.
1160    pub fn number_of_sessions(&self) -> usize {
1161        self.inner.info.sessions.len()
1162    }
1163
1164    /// Returns one session sector range by index.
1165    pub fn session(&self, index: usize) -> Option<&SectorRange> {
1166        self.inner.info.sessions.get(index)
1167    }
1168
1169    /// Returns track sector ranges.
1170    pub fn tracks(&self) -> &[SectorRange] {
1171        &self.inner.info.tracks
1172    }
1173
1174    /// Returns the number of track sector ranges.
1175    pub fn number_of_tracks(&self) -> usize {
1176        self.inner.info.tracks.len()
1177    }
1178
1179    /// Returns one track sector range by index.
1180    pub fn track(&self, index: usize) -> Option<&SectorRange> {
1181        self.inner.info.tracks.get(index)
1182    }
1183
1184    /// Returns whether checksum-failed chunks are read as zero-filled data.
1185    pub fn read_zero_chunk_on_error(&self) -> bool {
1186        self.inner.read_zero_chunk_on_error.load(Ordering::Relaxed)
1187    }
1188
1189    /// Sets whether checksum-failed chunks are read as zero-filled data.
1190    pub fn set_read_zero_chunk_on_error(&self, zero_on_error: bool) {
1191        self.inner
1192            .read_zero_chunk_on_error
1193            .store(zero_on_error, Ordering::Relaxed);
1194    }
1195
1196    /// Signals future reads and verification to abort with [`EwfError::Aborted`].
1197    pub fn signal_abort(&self) {
1198        self.inner.abort_signaled.store(true, Ordering::Relaxed);
1199    }
1200
1201    /// Returns checksum error ranges observed while reading.
1202    ///
1203    /// # Errors
1204    ///
1205    /// Returns an error if the internal checksum-error lock is poisoned.
1206    pub fn checksum_errors(&self) -> Result<Vec<SectorRange>> {
1207        Ok(self
1208            .inner
1209            .checksum_errors
1210            .lock()
1211            .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?
1212            .clone())
1213    }
1214
1215    /// Returns the number of checksum error ranges observed while reading.
1216    ///
1217    /// # Errors
1218    ///
1219    /// Returns an error if the internal checksum-error lock is poisoned.
1220    pub fn number_of_checksum_errors(&self) -> Result<usize> {
1221        Ok(self
1222            .inner
1223            .checksum_errors
1224            .lock()
1225            .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?
1226            .len())
1227    }
1228
1229    /// Returns one checksum error range observed while reading.
1230    ///
1231    /// # Errors
1232    ///
1233    /// Returns an error if the internal checksum-error lock is poisoned.
1234    pub fn checksum_error(&self, index: usize) -> Result<Option<SectorRange>> {
1235        Ok(self
1236            .inner
1237            .checksum_errors
1238            .lock()
1239            .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?
1240            .get(index)
1241            .cloned())
1242    }
1243
1244    /// Returns a seekable cursor over a logical single-file entry.
1245    pub fn single_file_cursor(&self, entry: &SingleFileEntry) -> SingleFileCursor {
1246        self.inner.statistics.record_cursor_created();
1247        SingleFileCursor {
1248            image: self.clone(),
1249            entry: entry.clone(),
1250            position: 0,
1251        }
1252    }
1253
1254    /// Returns a seekable cursor over a logical single-file entry by path.
1255    ///
1256    /// # Errors
1257    ///
1258    /// Returns an error if `path` contains an empty entry name.
1259    pub fn single_file_cursor_by_path(&self, path: &str) -> Result<Option<SingleFileCursor>> {
1260        self.file_entry_by_path(path)
1261            .map(|entry| entry.map(|entry| self.single_file_cursor(entry)))
1262    }
1263
1264    /// Reads and decodes one logical data chunk.
1265    ///
1266    /// # Errors
1267    ///
1268    /// Returns an error if the chunk index is invalid, segment I/O fails,
1269    /// decoding fails, checksum validation fails under the current policy, or
1270    /// [`Image::signal_abort`] has been called.
1271    pub fn read_data_chunk(&self, chunk_index: u64) -> Result<DataChunk> {
1272        self.ensure_not_aborted()?;
1273        let chunk = self.lookup_chunk(chunk_index)?;
1274        let (data, corrupted) = self.decode_chunk_with_policy(chunk_index, chunk)?;
1275        let logical_offset = chunk_index
1276            .checked_mul(self.inner.info.chunk_size)
1277            .ok_or_else(|| EwfError::Malformed("data chunk logical offset overflow".into()))?;
1278
1279        Ok(DataChunk {
1280            chunk_index,
1281            logical_offset,
1282            logical_size: chunk.logical_size,
1283            encoded_size: chunk.encoded_size,
1284            encoding: data_chunk_encoding(chunk.encoding),
1285            corrupted,
1286            data,
1287        })
1288    }
1289
1290    /// Reads one encoded data chunk without decoding the payload.
1291    ///
1292    /// # Errors
1293    ///
1294    /// Returns an error if the chunk index is invalid, segment I/O fails, or
1295    /// [`Image::signal_abort`] has been called.
1296    pub fn read_encoded_data_chunk(&self, chunk_index: u64) -> Result<EncodedDataChunk> {
1297        self.ensure_not_aborted()?;
1298        let chunk = self.lookup_chunk(chunk_index)?;
1299        let data = self.read_encoded_chunk_bytes(chunk)?;
1300        let logical_offset = chunk_index
1301            .checked_mul(self.inner.info.chunk_size)
1302            .ok_or_else(|| EwfError::Malformed("data chunk logical offset overflow".into()))?;
1303
1304        Ok(EncodedDataChunk {
1305            chunk_index,
1306            logical_offset,
1307            logical_size: chunk.logical_size,
1308            encoded_size: chunk.encoded_size,
1309            encoding: data_chunk_encoding(chunk.encoding),
1310            has_checksum: chunk.validate_checksum,
1311            data,
1312        })
1313    }
1314
1315    fn read_chunk(&self, chunk_id: u64) -> Result<Arc<Vec<u8>>> {
1316        self.ensure_not_aborted()?;
1317        let cached = self
1318            .inner
1319            .chunk_cache
1320            .lock()
1321            .map_err(|_| EwfError::Malformed("chunk cache lock poisoned".into()))?
1322            .get(&chunk_id)
1323            .cloned();
1324        self.inner
1325            .statistics
1326            .record_chunk_cache_access(cached.is_some());
1327        if let Some(cached) = cached {
1328            return Ok(cached);
1329        }
1330
1331        let chunk = self.lookup_chunk(chunk_id)?;
1332        let (decoded, _) = self.decode_chunk_with_policy(chunk_id, chunk)?;
1333        let decoded = Arc::new(decoded);
1334        self.inner
1335            .chunk_cache
1336            .lock()
1337            .map_err(|_| EwfError::Malformed("chunk cache lock poisoned".into()))?
1338            .put(chunk_id, Arc::clone(&decoded));
1339        Ok(decoded)
1340    }
1341
1342    fn lookup_chunk(&self, chunk_id: u64) -> Result<Chunk> {
1343        let (_, range) = self.inner.index.range_index_for(chunk_id)?;
1344        let local_index = chunk_id
1345            .checked_sub(range.first_chunk)
1346            .ok_or_else(|| EwfError::Malformed("chunk range underflow".into()))?;
1347        let logical_size = logical_chunk_size(
1348            self.inner.info.logical_size,
1349            self.inner.info.chunk_size,
1350            chunk_id,
1351        )?;
1352
1353        match range.kind {
1354            TableRangeKind::Ewf1 => self.lookup_ewf1_chunk(range, local_index, logical_size),
1355            TableRangeKind::Ewf2 => self.lookup_ewf2_chunk(range, local_index, logical_size),
1356        }
1357    }
1358
1359    fn lookup_ewf1_chunk(
1360        &self,
1361        range: &TableRange,
1362        local_index: u64,
1363        logical_size: usize,
1364    ) -> Result<Chunk> {
1365        let raw = self.read_u32_at(
1366            range.segment_index,
1367            table_entry_offset(range, local_index, 4)?,
1368        )?;
1369        let next_raw = if local_index + 1 < range.chunk_count {
1370            Some(self.read_u32_at(
1371                range.segment_index,
1372                table_entry_offset(range, local_index + 1, 4)?,
1373            )?)
1374        } else {
1375            None
1376        };
1377        let entry = decode_ewf1_entry(
1378            range,
1379            raw,
1380            self.inner.info.chunk_size,
1381            next_raw,
1382            local_index + 1 == range.chunk_count,
1383        )?;
1384
1385        let next_offset = if let Some(next_raw) = next_raw {
1386            decode_ewf1_entry(
1387                range,
1388                next_raw,
1389                self.inner.info.chunk_size,
1390                None,
1391                local_index + 2 == range.chunk_count,
1392            )?
1393            .offset
1394        } else {
1395            range
1396                .data_end
1397                .ok_or_else(|| EwfError::Malformed("EWF1 table range has no data end".into()))?
1398        };
1399
1400        if next_offset <= entry.offset {
1401            return Err(EwfError::Malformed(
1402                "EWF1 chunk offsets are not ordered".into(),
1403            ));
1404        }
1405        let encoded_size = next_offset - entry.offset;
1406        let encoding =
1407            ewf1_chunk_encoding(entry.compressed, encoded_size, self.inner.info.chunk_size)?;
1408        validate_ewf1_encoded_size(
1409            encoded_size,
1410            self.inner.info.chunk_size,
1411            encoding,
1412            range.ewf1_allow_large_compressed_chunks,
1413        )?;
1414        Ok(Chunk {
1415            segment_index: range.segment_index,
1416            offset: entry.offset,
1417            encoded_size,
1418            logical_size,
1419            encoding,
1420            validate_checksum: encoding == ChunkEncoding::Raw
1421                && u64::try_from(logical_size)
1422                    .ok()
1423                    .and_then(|size| size.checked_add(4))
1424                    == Some(encoded_size),
1425        })
1426    }
1427
1428    fn lookup_ewf2_chunk(
1429        &self,
1430        range: &TableRange,
1431        local_index: u64,
1432        logical_size: usize,
1433    ) -> Result<Chunk> {
1434        let entry_offset = table_entry_offset(range, local_index, ewf2::TABLE_ENTRY_SIZE as u64)?;
1435        let entry_data = self.read_table_bytes_at(
1436            range.segment_index,
1437            entry_offset,
1438            ewf2::TABLE_ENTRY_SIZE as u64,
1439        )?;
1440        let compression_method = range
1441            .ewf2_compression_method
1442            .map(ewf2::CompressionMethod::from)
1443            .ok_or_else(|| EwfError::Malformed("EWF2 range has no compression method".into()))?;
1444        let entry = ewf2::TableEntry::parse(&entry_data, compression_method)?;
1445        let encoding = match entry.kind {
1446            ewf2::ChunkKind::Raw | ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::None) => {
1447                ChunkEncoding::Raw
1448            }
1449            ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::Zlib) => ChunkEncoding::Zlib,
1450            ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::Bzip2) => ChunkEncoding::Bzip2,
1451            ewf2::ChunkKind::Compressed(ewf2::CompressionMethod::Unknown(method)) => {
1452                return Err(EwfError::Unsupported(format!(
1453                    "unknown EWF2 compression method {method}"
1454                )));
1455            }
1456            ewf2::ChunkKind::PatternFill => ChunkEncoding::PatternFill(entry.chunk_data_offset),
1457        };
1458        let validate_checksum = matches!(encoding, ChunkEncoding::Raw)
1459            && entry.flags & ewf2::CHUNK_FLAG_HAS_CHECKSUM != 0;
1460        validate_encoded_size(
1461            u64::from(entry.chunk_data_size),
1462            self.inner.info.chunk_size,
1463            encoding,
1464        )?;
1465        Ok(Chunk {
1466            segment_index: range.segment_index,
1467            offset: entry.chunk_data_offset,
1468            encoded_size: u64::from(entry.chunk_data_size),
1469            logical_size,
1470            encoding,
1471            validate_checksum,
1472        })
1473    }
1474
1475    fn read_u32_at(&self, segment_index: usize, offset: u64) -> Result<u32> {
1476        let data = self.read_table_bytes_at(segment_index, offset, 4)?;
1477        Ok(u32::from_le_bytes(
1478            data[..4].try_into().expect("slice length checked"),
1479        ))
1480    }
1481
1482    fn read_table_bytes_at(&self, segment_index: usize, offset: u64, size: u64) -> Result<Vec<u8>> {
1483        let requested = usize::try_from(size)
1484            .map_err(|_| EwfError::Malformed("table read size does not fit usize".into()))?;
1485        let cache_disabled = self
1486            .inner
1487            .table_page_cache
1488            .lock()
1489            .map_err(|_| EwfError::Malformed("table page cache lock poisoned".into()))?
1490            .is_disabled();
1491        if cache_disabled {
1492            self.inner.statistics.record_table_page_cache_access(false);
1493            let path = self
1494                .inner
1495                .info
1496                .segment_paths
1497                .get(segment_index)
1498                .ok_or_else(|| EwfError::Malformed("table references missing segment".into()))?
1499                .clone();
1500            let mut segments = self
1501                .inner
1502                .segments
1503                .lock()
1504                .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?;
1505            let file = segments.file_mut(segment_index, &path)?;
1506            return read_exact_at(file.as_mut(), offset, size);
1507        }
1508        let mut output = Vec::with_capacity(requested);
1509        let mut current = offset;
1510        while output.len() < requested {
1511            let page_offset = current - current % TABLE_PAGE_SIZE;
1512            let key = TablePageKey {
1513                segment_index,
1514                page_offset,
1515            };
1516            let cached = self
1517                .inner
1518                .table_page_cache
1519                .lock()
1520                .map_err(|_| EwfError::Malformed("table page cache lock poisoned".into()))?
1521                .get(&key);
1522            self.inner
1523                .statistics
1524                .record_table_page_cache_access(cached.is_some());
1525            let page = if let Some(page) = cached {
1526                page
1527            } else {
1528                let path = self
1529                    .inner
1530                    .info
1531                    .segment_paths
1532                    .get(segment_index)
1533                    .ok_or_else(|| EwfError::Malformed("table references missing segment".into()))?
1534                    .clone();
1535                let bytes = {
1536                    let mut segments = self.inner.segments.lock().map_err(|_| {
1537                        EwfError::Malformed("segment file pool lock poisoned".into())
1538                    })?;
1539                    let segment_len = segments.segment_len(segment_index, &path)?;
1540                    let page_size = TABLE_PAGE_SIZE.min(segment_len.saturating_sub(page_offset));
1541                    if page_size == 0 {
1542                        return Err(EwfError::Malformed(
1543                            "table page starts beyond segment end".into(),
1544                        ));
1545                    }
1546                    let file = segments.file_mut(segment_index, &path)?;
1547                    read_exact_at(file.as_mut(), page_offset, page_size)?
1548                };
1549                self.inner
1550                    .table_page_cache
1551                    .lock()
1552                    .map_err(|_| EwfError::Malformed("table page cache lock poisoned".into()))?
1553                    .insert(key, bytes)
1554            };
1555            let within_page = usize::try_from(current - page_offset)
1556                .map_err(|_| EwfError::Malformed("table page offset does not fit usize".into()))?;
1557            let available = page.len().saturating_sub(within_page);
1558            if available == 0 {
1559                return Err(EwfError::Malformed("table read crosses segment end".into()));
1560            }
1561            let take = available.min(requested - output.len());
1562            output.extend_from_slice(&page[within_page..within_page + take]);
1563            current = current
1564                .checked_add(u64::try_from(take).expect("usize fits u64"))
1565                .ok_or_else(|| EwfError::Malformed("table read offset overflow".into()))?;
1566        }
1567        Ok(output)
1568    }
1569
1570    fn ensure_not_aborted(&self) -> Result<()> {
1571        if self.inner.abort_signaled.load(Ordering::Relaxed) {
1572            return Err(EwfError::Aborted);
1573        }
1574        Ok(())
1575    }
1576
1577    fn has_supplied_segment_readers(&self) -> Result<bool> {
1578        Ok(self
1579            .inner
1580            .segments
1581            .lock()
1582            .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?
1583            .has_supplied_readers())
1584    }
1585
1586    fn read_encoded_chunk_bytes(&self, chunk: Chunk) -> Result<Vec<u8>> {
1587        self.ensure_not_aborted()?;
1588        if matches!(chunk.encoding, ChunkEncoding::PatternFill(_)) {
1589            return Ok(Vec::new());
1590        }
1591
1592        let encoded_size = usize::try_from(chunk.encoded_size)
1593            .map_err(|_| EwfError::Malformed("encoded chunk size does not fit usize".into()))?;
1594        let path = self
1595            .inner
1596            .info
1597            .segment_paths
1598            .get(chunk.segment_index)
1599            .ok_or_else(|| EwfError::Malformed("chunk references missing segment".into()))?
1600            .clone();
1601        let mut segments = self
1602            .inner
1603            .segments
1604            .lock()
1605            .map_err(|_| EwfError::Malformed("segment file pool lock poisoned".into()))?;
1606        let segment_size = segments.segment_len(chunk.segment_index, &path)?;
1607        let file = segments.file_mut(chunk.segment_index, &path)?;
1608        let end = chunk
1609            .offset
1610            .checked_add(chunk.encoded_size)
1611            .ok_or_else(|| EwfError::Malformed("chunk byte range overflow".into()))?;
1612        if end > segment_size {
1613            return Err(EwfError::Malformed(format!(
1614                "chunk byte range {}..{} exceeds segment size {}",
1615                chunk.offset, end, segment_size
1616            )));
1617        }
1618
1619        let mut encoded = vec![0; encoded_size];
1620        file.seek(SeekFrom::Start(chunk.offset))?;
1621        file.read_exact(&mut encoded)?;
1622        self.inner
1623            .statistics
1624            .record_encoded_bytes_read(chunk.encoded_size);
1625        Ok(encoded)
1626    }
1627
1628    fn decode_chunk(&self, chunk: Chunk) -> Result<Vec<u8>> {
1629        let encoded = self.read_encoded_chunk_bytes(chunk)?;
1630        if chunk.validate_checksum {
1631            validate_raw_chunk_checksum(&encoded, chunk.logical_size)?;
1632        }
1633        let decompression_started = (self.inner.statistics.enabled()
1634            && matches!(chunk.encoding, ChunkEncoding::Zlib | ChunkEncoding::Bzip2))
1635        .then(Instant::now);
1636        let decoded = decode_chunk(&encoded, chunk.encoding, chunk.logical_size)?;
1637        if let Some(started) = decompression_started {
1638            self.inner
1639                .statistics
1640                .record_decompression(started.elapsed());
1641        }
1642        self.inner.statistics.record_decoded_bytes(decoded.len());
1643        Ok(decoded)
1644    }
1645
1646    fn decode_chunk_with_policy(&self, chunk_id: u64, chunk: Chunk) -> Result<(Vec<u8>, bool)> {
1647        match self.decode_chunk(chunk) {
1648            Ok(decoded) => Ok((decoded, false)),
1649            Err(EwfError::Malformed(_)) if self.read_zero_chunk_on_error() => {
1650                self.record_checksum_error(chunk_id, chunk.logical_size)?;
1651                Ok((vec![0; chunk.logical_size], true))
1652            }
1653            Err(err) => Err(err),
1654        }
1655    }
1656
1657    fn record_checksum_error(&self, chunk_id: u64, logical_size: usize) -> Result<()> {
1658        let range = checksum_error_range(&self.inner.info, chunk_id, logical_size)?;
1659        let mut errors = self
1660            .inner
1661            .checksum_errors
1662            .lock()
1663            .map_err(|_| EwfError::Malformed("checksum errors lock poisoned".into()))?;
1664        if !errors.contains(&range) {
1665            errors.push(range);
1666        }
1667        Ok(())
1668    }
1669}
1670
1671impl SegmentFilePool {
1672    fn new_path(
1673        segment_count: usize,
1674        maximum_open_handles: Option<usize>,
1675        statistics: Arc<ReaderStatisticsCollector>,
1676    ) -> Result<Self> {
1677        validate_maximum_open_handles(maximum_open_handles)?;
1678        Ok(Self {
1679            files: (0..segment_count).map(|_| None).collect(),
1680            lengths: vec![None; segment_count],
1681            ever_opened: vec![false; segment_count],
1682            open_order: VecDeque::new(),
1683            maximum_open_handles,
1684            mode: SegmentFilePoolMode::ReopenFromPath,
1685            statistics,
1686        })
1687    }
1688
1689    fn new_readers(
1690        readers: Vec<SegmentReaderHandle>,
1691        maximum_open_handles: Option<usize>,
1692        statistics: Arc<ReaderStatisticsCollector>,
1693    ) -> Result<Self> {
1694        validate_maximum_open_handles(maximum_open_handles)?;
1695        let segment_count = readers.len();
1696        if maximum_open_handles.is_some_and(|maximum| maximum < segment_count) {
1697            return Err(EwfError::Unsupported(
1698                "maximum open handles cannot evict supplied segment readers".into(),
1699            ));
1700        }
1701
1702        statistics.record_segment_handle_open(segment_count);
1703        Ok(Self {
1704            files: readers.into_iter().map(Some).collect(),
1705            lengths: vec![None; segment_count],
1706            ever_opened: vec![true; segment_count],
1707            open_order: (0..segment_count).collect(),
1708            maximum_open_handles,
1709            mode: SegmentFilePoolMode::SuppliedReaders,
1710            statistics,
1711        })
1712    }
1713
1714    fn maximum_open_handles(&self) -> Option<usize> {
1715        self.maximum_open_handles
1716    }
1717
1718    fn segment_len(&mut self, segment_index: usize, path: &Path) -> Result<u64> {
1719        if let Some(length) = self
1720            .lengths
1721            .get(segment_index)
1722            .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?
1723        {
1724            return Ok(*length);
1725        }
1726
1727        let length = self.file_mut(segment_index, path)?.segment_len()?;
1728        let cached = self
1729            .lengths
1730            .get_mut(segment_index)
1731            .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
1732        *cached = Some(length);
1733        Ok(length)
1734    }
1735
1736    fn set_maximum_open_handles(&mut self, maximum_open_handles: Option<usize>) -> Result<()> {
1737        validate_maximum_open_handles(maximum_open_handles)?;
1738        if !self.can_close_handles()
1739            && maximum_open_handles.is_some_and(|maximum| maximum < self.open_count())
1740        {
1741            return Err(EwfError::Unsupported(
1742                "maximum open handles cannot evict supplied segment readers".into(),
1743            ));
1744        }
1745        let previous_maximum_open_handles = self.maximum_open_handles;
1746        self.maximum_open_handles = maximum_open_handles;
1747        if let Err(err) = self.enforce_limit() {
1748            self.maximum_open_handles = previous_maximum_open_handles;
1749            return Err(err);
1750        }
1751        Ok(())
1752    }
1753
1754    fn open_count(&self) -> usize {
1755        self.files.iter().filter(|file| file.is_some()).count()
1756    }
1757
1758    fn reserve_handle(&mut self) -> Result<()> {
1759        if let Some(maximum_open_handles) = self.maximum_open_handles {
1760            if self.open_count() >= maximum_open_handles && !self.can_close_handles() {
1761                return Err(EwfError::Unsupported(
1762                    "maximum open handles cannot evict supplied segment readers".into(),
1763                ));
1764            }
1765            while self.open_count() >= maximum_open_handles {
1766                self.close_least_recently_used()?;
1767            }
1768        }
1769        Ok(())
1770    }
1771
1772    fn file_mut(&mut self, segment_index: usize, path: &Path) -> Result<&mut SegmentReaderHandle> {
1773        let slot = self
1774            .files
1775            .get(segment_index)
1776            .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
1777        if slot.is_none() {
1778            self.reserve_handle()?;
1779            let was_opened = *self
1780                .ever_opened
1781                .get(segment_index)
1782                .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
1783            let file = match self.mode {
1784                SegmentFilePoolMode::ReopenFromPath => {
1785                    Some(Box::new(File::open(path)?) as SegmentReaderHandle)
1786                }
1787                SegmentFilePoolMode::SuppliedReaders => None,
1788            }
1789            .ok_or_else(|| {
1790                EwfError::Malformed("supplied segment reader was unexpectedly closed".into())
1791            })?;
1792            let slot = self
1793                .files
1794                .get_mut(segment_index)
1795                .ok_or_else(|| EwfError::Malformed("segment index out of range".into()))?;
1796            *slot = Some(file);
1797            self.ever_opened[segment_index] = true;
1798            if was_opened {
1799                self.statistics.record_segment_handle_reopen();
1800            } else {
1801                self.statistics.record_segment_handle_open(1);
1802            }
1803        }
1804        self.mark_used(segment_index);
1805        self.files[segment_index]
1806            .as_mut()
1807            .ok_or_else(|| EwfError::Malformed("segment file was not opened".into()))
1808    }
1809
1810    fn can_close_handles(&self) -> bool {
1811        self.mode == SegmentFilePoolMode::ReopenFromPath
1812    }
1813
1814    fn has_supplied_readers(&self) -> bool {
1815        self.mode == SegmentFilePoolMode::SuppliedReaders
1816    }
1817
1818    fn mark_used(&mut self, segment_index: usize) {
1819        if let Some(position) = self
1820            .open_order
1821            .iter()
1822            .position(|open_segment_index| *open_segment_index == segment_index)
1823        {
1824            self.open_order.remove(position);
1825        }
1826        self.open_order.push_back(segment_index);
1827    }
1828
1829    fn enforce_limit(&mut self) -> Result<()> {
1830        if let Some(maximum_open_handles) = self.maximum_open_handles {
1831            if self.open_count() > maximum_open_handles && !self.can_close_handles() {
1832                return Err(EwfError::Unsupported(
1833                    "maximum open handles cannot evict supplied segment readers".into(),
1834                ));
1835            }
1836            while self.open_count() > maximum_open_handles {
1837                self.close_least_recently_used()?;
1838            }
1839        }
1840        Ok(())
1841    }
1842
1843    fn close_least_recently_used(&mut self) -> Result<()> {
1844        while let Some(segment_index) = self.open_order.pop_front() {
1845            if let Some(slot) = self.files.get_mut(segment_index)
1846                && slot.take().is_some()
1847            {
1848                return Ok(());
1849            }
1850        }
1851        Err(EwfError::Malformed(
1852            "segment file pool has no open handle to close".into(),
1853        ))
1854    }
1855}
1856
1857fn validate_maximum_open_handles(maximum_open_handles: Option<usize>) -> Result<()> {
1858    if maximum_open_handles == Some(0) {
1859        return Err(EwfError::Unsupported(
1860            "maximum open handles must be at least one".into(),
1861        ));
1862    }
1863    Ok(())
1864}
1865
1866fn data_chunk_encoding(encoding: ChunkEncoding) -> DataChunkEncoding {
1867    match encoding {
1868        ChunkEncoding::Raw => DataChunkEncoding::Raw,
1869        ChunkEncoding::Zlib => DataChunkEncoding::Zlib,
1870        ChunkEncoding::Bzip2 => DataChunkEncoding::Bzip2,
1871        ChunkEncoding::PatternFill(pattern) => DataChunkEncoding::PatternFill(pattern),
1872    }
1873}
1874
1875impl ImageCursor {
1876    /// Returns the current logical media byte position.
1877    pub fn position(&self) -> u64 {
1878        self.position
1879    }
1880
1881    /// Alias for [`ImageCursor::position`].
1882    pub fn offset(&self) -> u64 {
1883        self.position()
1884    }
1885
1886    /// Reads logical media bytes at the current cursor position and advances.
1887    ///
1888    /// # Errors
1889    ///
1890    /// Returns an error if the underlying [`Image::read_at`] call fails or the
1891    /// cursor position would overflow.
1892    pub fn read_buffer(&mut self, buf: &mut [u8]) -> Result<usize> {
1893        let read = self.image.read_at(buf, self.position)?;
1894        self.position = self
1895            .position
1896            .checked_add(u64::try_from(read).expect("usize fits u64"))
1897            .ok_or_else(|| EwfError::Malformed("cursor position overflow".into()))?;
1898        Ok(read)
1899    }
1900
1901    /// Seeks to `offset`, reads into `buf`, and advances by the bytes read.
1902    ///
1903    /// # Errors
1904    ///
1905    /// Returns the same errors as [`ImageCursor::read_buffer`].
1906    pub fn read_buffer_at_offset(&mut self, buf: &mut [u8], offset: u64) -> Result<usize> {
1907        self.position = offset;
1908        self.read_buffer(buf)
1909    }
1910
1911    /// Seeks the cursor using [`SeekFrom`] and returns the new position.
1912    ///
1913    /// # Errors
1914    ///
1915    /// Returns an error if the seek would move before the start of the image or
1916    /// beyond `u64` addressable space.
1917    pub fn seek_offset(&mut self, pos: SeekFrom) -> Result<u64> {
1918        self.seek_position(pos).map_err(EwfError::from)
1919    }
1920
1921    /// Returns the segment containing the current cursor position.
1922    ///
1923    /// Positions at or beyond the logical media size return `Ok(None)`.
1924    ///
1925    /// # Errors
1926    ///
1927    /// Returns an error if the image chunk geometry is malformed.
1928    pub fn segment_filename(&self) -> Result<Option<&Path>> {
1929        self.image.segment_filename_for_offset(self.position)
1930    }
1931
1932    /// Reads the data chunk at the current cursor position and advances to the next chunk.
1933    ///
1934    /// Returns `Ok(None)` when the cursor is at or beyond the logical media end.
1935    ///
1936    /// # Errors
1937    ///
1938    /// Returns an error if reading the chunk fails or the cursor position would
1939    /// overflow.
1940    pub fn read_data_chunk(&mut self) -> Result<Option<DataChunk>> {
1941        let Some(chunk_index) = self.current_chunk_index()? else {
1942            return Ok(None);
1943        };
1944        let chunk = self.image.read_data_chunk(chunk_index)?;
1945        self.advance_to_chunk_end(chunk.logical_offset, chunk.logical_size)?;
1946        Ok(Some(chunk))
1947    }
1948
1949    /// Reads the encoded data chunk at the current cursor position and advances.
1950    ///
1951    /// Returns `Ok(None)` when the cursor is at or beyond the logical media end.
1952    ///
1953    /// # Errors
1954    ///
1955    /// Returns an error if reading the chunk fails or the cursor position would
1956    /// overflow.
1957    pub fn read_encoded_data_chunk(&mut self) -> Result<Option<EncodedDataChunk>> {
1958        let Some(chunk_index) = self.current_chunk_index()? else {
1959            return Ok(None);
1960        };
1961        let chunk = self.image.read_encoded_data_chunk(chunk_index)?;
1962        self.advance_to_chunk_end(chunk.logical_offset, chunk.logical_size)?;
1963        Ok(Some(chunk))
1964    }
1965
1966    fn current_chunk_index(&self) -> Result<Option<u64>> {
1967        if self.position >= self.image.info().logical_size {
1968            return Ok(None);
1969        }
1970        let chunk_size = self.image.info().chunk_size;
1971        if chunk_size == 0 {
1972            return Err(EwfError::Malformed("chunk size is zero".into()));
1973        }
1974        Ok(Some(self.position / chunk_size))
1975    }
1976
1977    fn advance_to_chunk_end(&mut self, logical_offset: u64, logical_size: usize) -> Result<()> {
1978        let logical_size = u64::try_from(logical_size)
1979            .map_err(|_| EwfError::Malformed("chunk logical size does not fit u64".into()))?;
1980        self.position = logical_offset
1981            .checked_add(logical_size)
1982            .ok_or_else(|| EwfError::Malformed("cursor chunk end offset overflow".into()))?;
1983        Ok(())
1984    }
1985
1986    fn seek_position(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
1987        let next = match pos {
1988            SeekFrom::Start(offset) => i128::from(offset),
1989            SeekFrom::End(offset) => {
1990                i128::from(self.image.info().logical_size) + i128::from(offset)
1991            }
1992            SeekFrom::Current(offset) => i128::from(self.position) + i128::from(offset),
1993        };
1994        if next < 0 {
1995            return Err(std::io::Error::new(
1996                std::io::ErrorKind::InvalidInput,
1997                "seek before start of image",
1998            ));
1999        }
2000        self.position = u64::try_from(next).map_err(|_| {
2001            std::io::Error::new(
2002                std::io::ErrorKind::InvalidInput,
2003                "seek position does not fit u64",
2004            )
2005        })?;
2006        Ok(self.position)
2007    }
2008}
2009
2010impl Read for ImageCursor {
2011    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2012        self.read_buffer(buf).map_err(std::io::Error::other)
2013    }
2014}
2015
2016impl Seek for ImageCursor {
2017    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
2018        self.seek_position(pos)
2019    }
2020}
2021
2022impl SingleFileCursor {
2023    /// Returns the current single-file byte position.
2024    pub fn position(&self) -> u64 {
2025        self.position
2026    }
2027
2028    /// Alias for [`SingleFileCursor::position`].
2029    pub fn offset(&self) -> u64 {
2030        self.position()
2031    }
2032
2033    /// Reads bytes at the current single-file position and advances.
2034    ///
2035    /// # Errors
2036    ///
2037    /// Returns an error if the underlying single-file read fails or the cursor
2038    /// position would overflow.
2039    pub fn read_buffer(&mut self, buf: &mut [u8]) -> Result<usize> {
2040        let read = self
2041            .image
2042            .read_single_file_at(&self.entry, buf, self.position)?;
2043        self.position = self
2044            .position
2045            .checked_add(u64::try_from(read).expect("usize fits u64"))
2046            .ok_or_else(|| EwfError::Malformed("single file cursor position overflow".into()))?;
2047        Ok(read)
2048    }
2049
2050    /// Seeks to `offset`, reads into `buf`, and advances by the bytes read.
2051    ///
2052    /// # Errors
2053    ///
2054    /// Returns the same errors as [`SingleFileCursor::read_buffer`].
2055    pub fn read_buffer_at_offset(&mut self, buf: &mut [u8], offset: u64) -> Result<usize> {
2056        self.position = offset;
2057        self.read_buffer(buf)
2058    }
2059
2060    /// Seeks the cursor using [`SeekFrom`] and returns the new position.
2061    ///
2062    /// # Errors
2063    ///
2064    /// Returns an error if the seek would move before the start of the file, the
2065    /// file size is unavailable, or the resulting position does not fit `u64`.
2066    pub fn seek_offset(&mut self, pos: SeekFrom) -> Result<u64> {
2067        self.seek_position(pos).map_err(EwfError::from)
2068    }
2069
2070    fn seek_position(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
2071        let file_size = single_file_size(&self.entry).map_err(std::io::Error::other)?;
2072        let next = match pos {
2073            SeekFrom::Start(offset) => i128::from(offset),
2074            SeekFrom::End(offset) => i128::from(file_size) + i128::from(offset),
2075            SeekFrom::Current(offset) => i128::from(self.position) + i128::from(offset),
2076        };
2077        if next < 0 {
2078            return Err(std::io::Error::new(
2079                std::io::ErrorKind::InvalidInput,
2080                "cannot seek before start of single file",
2081            ));
2082        }
2083        self.position = u64::try_from(next).map_err(|_| {
2084            std::io::Error::new(
2085                std::io::ErrorKind::InvalidInput,
2086                "single file cursor position does not fit u64",
2087            )
2088        })?;
2089        Ok(self.position)
2090    }
2091}
2092
2093impl Read for SingleFileCursor {
2094    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2095        self.read_buffer(buf).map_err(std::io::Error::other)
2096    }
2097}
2098
2099impl Seek for SingleFileCursor {
2100    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
2101        self.seek_position(pos)
2102    }
2103
2104    fn stream_position(&mut self) -> std::io::Result<u64> {
2105        Ok(self.position)
2106    }
2107}
2108
2109fn single_file_size(entry: &SingleFileEntry) -> Result<u64> {
2110    if let Some(size) = entry.size {
2111        return Ok(size);
2112    }
2113
2114    entry.extents.iter().try_fold(0_u64, |total, extent| {
2115        total
2116            .checked_add(extent.data_size)
2117            .ok_or_else(|| EwfError::Malformed("single file size overflow".into()))
2118    })
2119}
2120
2121struct ParsedSegment {
2122    format: Format,
2123    format_profile: FormatProfile,
2124    format_profile_hint_only: bool,
2125    segment_number: u64,
2126    set_identifier: Option<[u8; 16]>,
2127    ewf2_header_profile: Option<Ewf2HeaderProfile>,
2128    chunk_size: u64,
2129    logical_size: u64,
2130    acquisition_complete: bool,
2131    media: MediaInfo,
2132    ranges: Vec<TableRange>,
2133    table_chunk_count: u64,
2134    metadata: EwfMetadata,
2135    stored_hashes: StoredHashes,
2136    acquisition_errors: Vec<AcquisitionError>,
2137    memory_extents: Vec<MemoryExtent>,
2138    single_files: Option<SingleFilesInfo>,
2139    ewf2_single_files_tables: SingleFilesAuxTables,
2140    ewf2_increment_data: Vec<Vec<u8>>,
2141    ewf2_final_information: Option<Vec<u8>>,
2142    ewf2_restart_data: Option<String>,
2143    ewf2_analytical_data: Option<String>,
2144    sessions: Vec<SectorRange>,
2145    tracks: Vec<SectorRange>,
2146    ewf2_device_information: Option<Vec<u8>>,
2147    ewf2_case_data: Option<Vec<u8>>,
2148}
2149
2150#[derive(Clone, Copy)]
2151struct Ewf2HeaderProfile {
2152    major_version: u8,
2153    minor_version: u8,
2154    compression_method: ewf2::CompressionMethod,
2155}
2156
2157#[derive(Clone)]
2158struct Section {
2159    desc: ewf1::SectionDescriptor,
2160    data_offset: u64,
2161    data_size: u64,
2162}
2163
2164#[derive(Clone, Copy)]
2165struct Ewf2Section {
2166    desc: ewf2::SectionDescriptor,
2167    data_offset: u64,
2168    data_size: u64,
2169    layout: Ewf2SectionLayout,
2170}
2171
2172#[derive(Clone, Copy, PartialEq, Eq)]
2173enum Ewf2SectionLayout {
2174    LeadingDescriptor,
2175    TrailingDescriptor,
2176}
2177
2178fn parse_ewf1_segment(
2179    file: &mut dyn SegmentReader,
2180    segment_index: usize,
2181    first_chunk: u64,
2182    profile_hint: FormatProfile,
2183    header_codepage: HeaderCodepage,
2184    statistics: &ReaderStatisticsCollector,
2185) -> Result<ParsedSegment> {
2186    let mut header = [0; ewf1::FILE_HEADER_SIZE];
2187    file.seek(SeekFrom::Start(0))?;
2188    file.read_exact(&mut header)?;
2189    let file_header = ewf1::FileHeader::parse(&header)?;
2190
2191    let sections = scan_ewf1_sections(file)?;
2192    let acquisition_complete = ewf1_acquisition_complete(&sections);
2193    let volume = sections
2194        .iter()
2195        .find(|section| {
2196            matches!(
2197                section.desc.section_type.as_str(),
2198                "volume" | "disk" | "data"
2199            )
2200        })
2201        .ok_or_else(|| EwfError::Malformed("missing EWF1 media section".into()))
2202        .and_then(|section| {
2203            let data = read_exact_at(file, section.data_offset, section.data_size)?;
2204            validate_present_ewf1_media_checksum(&data, &section.desc.section_type)?;
2205            ewf1::Volume::parse(&data)
2206        })?;
2207    let chunk_size = volume.chunk_size()?;
2208    validate_chunk_size(chunk_size)?;
2209    let declared_logical_size = volume.logical_size()?;
2210    let logical_size = if declared_logical_size > 0 {
2211        declared_logical_size
2212    } else {
2213        chunk_size
2214            .checked_mul(u64::from(volume.chunk_count))
2215            .ok_or_else(|| EwfError::Malformed("EWF1 logical size overflow".into()))?
2216    };
2217    let smart_profile = !file_header.logical && volume.smart;
2218    let media = MediaInfo {
2219        sectors_per_chunk: Some(u64::from(volume.sectors_per_chunk)),
2220        bytes_per_sector: Some(u64::from(volume.bytes_per_sector)),
2221        sector_count: Some(volume.sector_count),
2222        chunk_count: Some(u64::from(volume.chunk_count)),
2223        error_granularity: volume.error_granularity.map(u64::from),
2224        set_identifier: volume.set_identifier,
2225        ewf2_segment_file_version: None,
2226        compression_method: Some(CompressionMethod::Zlib),
2227        compression_values: CompressionValues {
2228            level: volume
2229                .compression_level
2230                .map(|value| CompressionLevel::from_i8(value as i8))
2231                .unwrap_or_default(),
2232            ..CompressionValues::default()
2233        },
2234        media_type: volume
2235            .media_type
2236            .map(ewf1_media_type)
2237            .or_else(|| smart_profile.then_some(MediaType::Removable)),
2238        media_flags: ewf1_media_flags(volume.media_flags, file_header.logical || smart_profile),
2239    };
2240
2241    let mut metadata = EwfMetadata::default();
2242    let mut stored_hashes = StoredHashes::default();
2243    let mut acquisition_errors = Vec::new();
2244    let memory_extents = Vec::new();
2245    let mut single_files = None;
2246    let mut format_profile = if file_header.logical {
2247        FormatProfile::LogicalEnCase5
2248    } else if smart_profile {
2249        FormatProfile::Smart
2250    } else if profile_hint != FormatProfile::Unknown {
2251        profile_hint
2252    } else {
2253        FormatProfile::EnCase5
2254    };
2255    let mut format_profile_hint_only = !file_header.logical && !smart_profile;
2256    let mut format_profile_detected_from_header2 = false;
2257    let mut sessions = Vec::new();
2258    let mut tracks = Vec::new();
2259    for section in &sections {
2260        match section.desc.section_type.as_str() {
2261            "header" => {
2262                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2263                let payload = ewf1_metadata_payload(&data);
2264                let text = decode_header_bytes(&payload, header_codepage);
2265                if !format_profile_detected_from_header2
2266                    && apply_detected_ewf1_format_profile(
2267                        &mut format_profile,
2268                        detect_ewf1_header_profile(&text, 1),
2269                    )
2270                {
2271                    format_profile_hint_only = false;
2272                }
2273                parse_header_data(&payload, header_codepage, &mut metadata);
2274            }
2275            "header2" => {
2276                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2277                let payload = ewf1_metadata_payload(&data);
2278                if apply_detected_ewf1_format_profile(
2279                    &mut format_profile,
2280                    detect_ewf1_header2_profile(&payload),
2281                ) {
2282                    format_profile_hint_only = false;
2283                    format_profile_detected_from_header2 = true;
2284                }
2285                parse_header2_data(&payload, &mut metadata);
2286            }
2287            "xheader" => {
2288                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2289                let payload = ewf1_metadata_payload(&data);
2290                parse_xheader_data(&payload, &mut metadata);
2291            }
2292            "error2" => {
2293                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2294                acquisition_errors.extend(parse_error2_data(&data)?);
2295            }
2296            "session" => {
2297                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2298                let parsed_sessions = parse_session_data(&data, 1, volume.sector_count)?;
2299                sessions.extend(parsed_sessions.sessions);
2300                tracks.extend(parsed_sessions.tracks);
2301            }
2302            "hash" => {
2303                validate_hash_section_size(section.data_size, EWF1_HASH_SECTION_SIZE, "EWF1 MD5")?;
2304                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2305                validate_adler32_checksum(&data, 32, 32, "EWF1 MD5 hash")?;
2306                if stored_hashes.md5.is_none()
2307                    && let Some(hash) = parse_nonzero_hash(&data)
2308                {
2309                    stored_hashes.md5 = Some(hash);
2310                    insert_hash_value(&mut stored_hashes, "MD5", &hash);
2311                }
2312            }
2313            "digest" => {
2314                validate_hash_section_size(
2315                    section.data_size,
2316                    EWF1_DIGEST_SECTION_SIZE,
2317                    "EWF1 digest",
2318                )?;
2319                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2320                validate_adler32_checksum(&data, 76, 76, "EWF1 digest")?;
2321                if stored_hashes.md5.is_none()
2322                    && let Some(hash) = parse_nonzero_hash(&data[..16])
2323                {
2324                    stored_hashes.md5 = Some(hash);
2325                    insert_hash_value(&mut stored_hashes, "MD5", &hash);
2326                }
2327                if stored_hashes.sha1.is_none()
2328                    && let Some(hash) = parse_nonzero_hash(&data[16..36])
2329                {
2330                    stored_hashes.sha1 = Some(hash);
2331                    insert_hash_value(&mut stored_hashes, "SHA1", &hash);
2332                }
2333            }
2334            "xhash" => {
2335                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2336                let payload = ewf1_metadata_payload(&data);
2337                parse_xhash_data(&payload, &mut stored_hashes);
2338            }
2339            "ltree" => {
2340                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2341                merge_single_files(&mut single_files, Some(parse_ewf1_ltree_data(&data)?))?;
2342            }
2343            _ => {}
2344        }
2345    }
2346
2347    let ranges = parse_ewf1_ranges(
2348        file,
2349        &sections,
2350        segment_index,
2351        first_chunk,
2352        logical_size,
2353        volume.smart,
2354        statistics,
2355    )?;
2356    let table_chunk_count = ranges.iter().try_fold(0_u64, |count, range| {
2357        count
2358            .checked_add(range.chunk_count)
2359            .ok_or_else(|| EwfError::Malformed("EWF1 table chunk count overflow".into()))
2360    })?;
2361    Ok(ParsedSegment {
2362        format: Format::Ewf1,
2363        format_profile,
2364        format_profile_hint_only,
2365        segment_number: u64::from(file_header.segment_number),
2366        set_identifier: volume.set_identifier,
2367        ewf2_header_profile: None,
2368        chunk_size,
2369        logical_size,
2370        acquisition_complete,
2371        media,
2372        ranges,
2373        table_chunk_count,
2374        metadata,
2375        stored_hashes,
2376        acquisition_errors,
2377        memory_extents,
2378        single_files,
2379        ewf2_single_files_tables: SingleFilesAuxTables::default(),
2380        ewf2_increment_data: Vec::new(),
2381        ewf2_final_information: None,
2382        ewf2_restart_data: None,
2383        ewf2_analytical_data: None,
2384        sessions,
2385        tracks,
2386        ewf2_device_information: None,
2387        ewf2_case_data: None,
2388    })
2389}
2390
2391fn ewf1_media_type(value: u8) -> MediaType {
2392    match value {
2393        0x00 => MediaType::Removable,
2394        0x01 => MediaType::Fixed,
2395        0x03 => MediaType::Optical,
2396        0x0e => MediaType::SingleFiles,
2397        0x10 => MediaType::Memory,
2398        value => MediaType::Unknown(value),
2399    }
2400}
2401
2402fn ewf1_media_flags(value: Option<u8>, logical_file_header: bool) -> MediaFlags {
2403    value.map_or(
2404        MediaFlags {
2405            physical: !logical_file_header,
2406            fastbloc: false,
2407            tableau: false,
2408        },
2409        |value| MediaFlags {
2410            physical: value & 0x02 != 0,
2411            fastbloc: value & 0x04 != 0,
2412            tableau: value & 0x08 != 0,
2413        },
2414    )
2415}
2416
2417fn ewf1_format_profile_hint_from_path(path: &Path) -> FormatProfile {
2418    match path.extension().and_then(|extension| extension.to_str()) {
2419        Some(extension) if extension.starts_with('e') => FormatProfile::Ewf,
2420        Some(extension) if extension.starts_with('E') => FormatProfile::EnCase2,
2421        Some(extension) if extension.starts_with('L') => FormatProfile::LogicalEnCase5,
2422        Some(extension) if extension.starts_with('s') || extension.starts_with('S') => {
2423            FormatProfile::Smart
2424        }
2425        _ => FormatProfile::Unknown,
2426    }
2427}
2428
2429fn parse_segment(
2430    file: &mut dyn SegmentReader,
2431    path: &Path,
2432    segment_index: usize,
2433    first_ewf1_chunk: u64,
2434    strictness: OpenStrictness,
2435    header_codepage: HeaderCodepage,
2436    statistics: &ReaderStatisticsCollector,
2437) -> Result<ParsedSegment> {
2438    let mut signature = [0; 8];
2439    file.seek(SeekFrom::Start(0))?;
2440    file.read_exact(&mut signature)?;
2441    if signature == ewf1::EVF_SIGNATURE || signature == ewf1::LVF_SIGNATURE {
2442        parse_ewf1_segment(
2443            file,
2444            segment_index,
2445            first_ewf1_chunk,
2446            ewf1_format_profile_hint_from_path(path),
2447            header_codepage,
2448            statistics,
2449        )
2450    } else if signature == ewf2::EX01_SIGNATURE || signature == ewf2::LEF2_SIGNATURE {
2451        parse_ewf2_segment(file, segment_index, strictness, statistics)
2452    } else {
2453        Err(EwfError::InvalidSignature)
2454    }
2455}
2456
2457fn parse_ewf2_segment(
2458    file: &mut dyn SegmentReader,
2459    segment_index: usize,
2460    strictness: OpenStrictness,
2461    statistics: &ReaderStatisticsCollector,
2462) -> Result<ParsedSegment> {
2463    let mut header = [0; ewf2::FILE_HEADER_SIZE];
2464    file.seek(SeekFrom::Start(0))?;
2465    file.read_exact(&mut header)?;
2466    let header = ewf2::FileHeader::parse(&header)?;
2467    let sections = scan_ewf2_sections(file, strictness)?;
2468    let acquisition_complete = ewf2_acquisition_complete(&sections);
2469
2470    let mut chunk_size = 0;
2471    let mut logical_size = 0;
2472    let mut metadata = EwfMetadata::default();
2473    let mut stored_hashes = StoredHashes::default();
2474    let mut acquisition_errors = Vec::new();
2475    let mut memory_extents = Vec::new();
2476    let mut single_files = None;
2477    let mut ewf2_single_files_tables = SingleFilesAuxTables::default();
2478    let mut ewf2_increment_data = Vec::new();
2479    let mut ewf2_final_information = None;
2480    let mut ewf2_restart_data = None;
2481    let mut ewf2_analytical_data = None;
2482    let mut session_table_data = Vec::new();
2483    let mut sessions = Vec::new();
2484    let mut tracks = Vec::new();
2485    let mut media = MediaInfo {
2486        sectors_per_chunk: None,
2487        bytes_per_sector: None,
2488        sector_count: None,
2489        chunk_count: None,
2490        error_granularity: None,
2491        set_identifier: Some(header.set_identifier),
2492        ewf2_segment_file_version: Some(SegmentFileVersion {
2493            major: header.major_version,
2494            minor: header.minor_version,
2495        }),
2496        compression_method: Some(public_compression_method(header.compression_method)),
2497        compression_values: CompressionValues::default(),
2498        media_type: None,
2499        media_flags: MediaFlags {
2500            physical: !header.logical,
2501            fastbloc: false,
2502            tableau: false,
2503        },
2504    };
2505    let mut device_information = None;
2506    let mut case_data = None;
2507    for section in &sections {
2508        match section.desc.section_type {
2509            ewf2::SectionType::DeviceInformation => {
2510                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2511                let data = ewf2_metadata_payload(&data, header.compression_method)?;
2512                remember_ewf2_metadata_payload(
2513                    &mut device_information,
2514                    &data,
2515                    "device information",
2516                )?;
2517                parse_ewf2_device_info_values(&data, &mut metadata);
2518                let geometry = parse_ewf2_device_info(&data)?;
2519                if let Some(size) = geometry.chunk_size {
2520                    chunk_size = size;
2521                }
2522                if let Some(size) = geometry.logical_size {
2523                    logical_size = size;
2524                }
2525                apply_ewf2_geometry(&mut media, geometry);
2526            }
2527            ewf2::SectionType::CaseData => {
2528                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2529                let data = ewf2_metadata_payload(&data, header.compression_method)?;
2530                remember_ewf2_metadata_payload(&mut case_data, &data, "case data")?;
2531                parse_ewf2_case_data(&data, &mut metadata);
2532                let geometry = parse_ewf2_device_info(&data)?;
2533                if chunk_size == 0
2534                    && let Some(size) = geometry.chunk_size
2535                {
2536                    chunk_size = size;
2537                }
2538                if logical_size == 0
2539                    && let Some(size) = geometry.logical_size
2540                {
2541                    logical_size = size;
2542                }
2543                apply_ewf2_geometry_if_missing(&mut media, geometry);
2544            }
2545            ewf2::SectionType::Md5Hash => {
2546                validate_hash_section_size(section.data_size, EWF2_HASH_SECTION_SIZE, "EWF2 MD5")?;
2547                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2548                validate_adler32_checksum(&data, 16, 16, "EWF2 MD5 hash")?;
2549                if stored_hashes.md5.is_none()
2550                    && let Some(hash) = parse_nonzero_hash(&data)
2551                {
2552                    stored_hashes.md5 = Some(hash);
2553                    insert_hash_value(&mut stored_hashes, "MD5", &hash);
2554                }
2555            }
2556            ewf2::SectionType::Sha1Hash => {
2557                validate_hash_section_size(section.data_size, EWF2_HASH_SECTION_SIZE, "EWF2 SHA1")?;
2558                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2559                validate_adler32_checksum(&data, 20, 20, "EWF2 SHA1 hash")?;
2560                if stored_hashes.sha1.is_none()
2561                    && let Some(hash) = parse_nonzero_hash(&data)
2562                {
2563                    stored_hashes.sha1 = Some(hash);
2564                    insert_hash_value(&mut stored_hashes, "SHA1", &hash);
2565                }
2566            }
2567            ewf2::SectionType::ErrorTable => {
2568                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2569                acquisition_errors.extend(parse_ewf2_error_table_data(&data)?);
2570            }
2571            ewf2::SectionType::MemoryExtentsTable => {
2572                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2573                memory_extents.extend(parse_ewf2_memory_extents_table(&data)?);
2574            }
2575            ewf2::SectionType::SingleFilesData => {
2576                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2577                merge_single_files(
2578                    &mut single_files,
2579                    Some(parse_ewf2_single_files_data(&data)?),
2580                )?;
2581            }
2582            ewf2::SectionType::SingleFilesTable => {
2583                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2584                merge_single_files_aux_u64_table(
2585                    &mut ewf2_single_files_tables.table_0x21_entries,
2586                    parse_ewf2_single_files_aux_u64_table(&data, "EWF2 single files 0x21 table")?,
2587                    "0x21",
2588                )?;
2589            }
2590            ewf2::SectionType::SingleFilesMd5HashTable => {
2591                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2592                merge_single_files_aux_md5_table(
2593                    &mut ewf2_single_files_tables.md5_hashes,
2594                    parse_ewf2_single_files_md5_hash_table(&data)?,
2595                )?;
2596            }
2597            ewf2::SectionType::SingleFilesUnknownTable => {
2598                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2599                merge_single_files_aux_u64_table(
2600                    &mut ewf2_single_files_tables.table_0x23_entries,
2601                    parse_ewf2_single_files_aux_u64_table(&data, "EWF2 single files 0x23 table")?,
2602                    "0x23",
2603                )?;
2604            }
2605            ewf2::SectionType::IncrementData => {
2606                ewf2_increment_data.push(read_exact_at(
2607                    file,
2608                    section.data_offset,
2609                    section.data_size,
2610                )?);
2611            }
2612            ewf2::SectionType::FinalInformation => {
2613                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2614                merge_optional_ewf2_raw_section(
2615                    &mut ewf2_final_information,
2616                    Some(data),
2617                    "final information",
2618                )?;
2619            }
2620            ewf2::SectionType::RestartData => {
2621                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2622                ewf2_restart_data = Some(decode_ewf2_string_section(
2623                    &data,
2624                    header.compression_method,
2625                    "restart data",
2626                )?);
2627            }
2628            ewf2::SectionType::AnalyticalData => {
2629                let data = read_exact_at(file, section.data_offset, section.data_size)?;
2630                ewf2_analytical_data = Some(decode_ewf2_string_section(
2631                    &data,
2632                    header.compression_method,
2633                    "analytical data",
2634                )?);
2635            }
2636            ewf2::SectionType::SessionTable => {
2637                session_table_data.push(read_exact_at(
2638                    file,
2639                    section.data_offset,
2640                    section.data_size,
2641                )?);
2642            }
2643            _ => {}
2644        }
2645    }
2646
2647    let media_sector_count = media.sector_count.unwrap_or(0);
2648    for data in session_table_data {
2649        let parsed_sessions = parse_session_data(&data, 2, media_sector_count)?;
2650        sessions.extend(parsed_sessions.sessions);
2651        tracks.extend(parsed_sessions.tracks);
2652    }
2653
2654    if chunk_size == 0 {
2655        chunk_size = 32_768;
2656    }
2657    validate_chunk_size(chunk_size)?;
2658    let ranges = parse_ewf2_ranges(
2659        file,
2660        &sections,
2661        segment_index,
2662        logical_size,
2663        header.compression_method,
2664        statistics,
2665    )?;
2666    if logical_size == 0 {
2667        let discovered_chunks = ranges.iter().try_fold(0_u64, |max, range| {
2668            let end = range
2669                .first_chunk
2670                .checked_add(range.chunk_count)
2671                .ok_or_else(|| EwfError::Malformed("EWF2 table chunk count overflow".into()))?;
2672            Ok::<u64, EwfError>(max.max(end))
2673        })?;
2674        logical_size = chunk_size
2675            .checked_mul(discovered_chunks)
2676            .ok_or_else(|| EwfError::Malformed("EWF2 logical size overflow".into()))?;
2677    }
2678    let table_chunk_count = ranges.iter().try_fold(0_u64, |count, range| {
2679        count
2680            .checked_add(range.chunk_count)
2681            .ok_or_else(|| EwfError::Malformed("EWF2 table chunk count overflow".into()))
2682    })?;
2683
2684    Ok(ParsedSegment {
2685        format: Format::Ewf2,
2686        format_profile: if header.logical {
2687            FormatProfile::Ewf2LogicalEnCase7
2688        } else {
2689            FormatProfile::Ewf2EnCase7
2690        },
2691        format_profile_hint_only: false,
2692        segment_number: u64::from(header.segment_number),
2693        set_identifier: Some(header.set_identifier),
2694        ewf2_header_profile: Some(Ewf2HeaderProfile {
2695            major_version: header.major_version,
2696            minor_version: header.minor_version,
2697            compression_method: header.compression_method,
2698        }),
2699        chunk_size,
2700        logical_size,
2701        acquisition_complete,
2702        media,
2703        ranges,
2704        table_chunk_count,
2705        metadata,
2706        stored_hashes,
2707        acquisition_errors,
2708        memory_extents,
2709        single_files,
2710        ewf2_single_files_tables,
2711        ewf2_increment_data,
2712        ewf2_final_information,
2713        ewf2_restart_data,
2714        ewf2_analytical_data,
2715        sessions,
2716        tracks,
2717        ewf2_device_information: device_information,
2718        ewf2_case_data: case_data,
2719    })
2720}
2721
2722fn scan_ewf1_sections(file: &mut dyn SegmentReader) -> Result<Vec<Section>> {
2723    let file_len = file.segment_len()?;
2724    let mut sections = Vec::new();
2725    let mut offset = ewf1::FILE_HEADER_SIZE as u64;
2726    loop {
2727        let descriptor_end = offset
2728            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2729            .ok_or_else(|| EwfError::Malformed("EWF1 section descriptor overflow".into()))?;
2730        if descriptor_end > file_len {
2731            return Err(EwfError::Malformed(
2732                "EWF1 section descriptor exceeds file".into(),
2733            ));
2734        }
2735        let mut buf = [0; ewf1::SECTION_DESCRIPTOR_SIZE];
2736        file.seek(SeekFrom::Start(offset))?;
2737        file.read_exact(&mut buf)?;
2738        validate_present_adler32_checksum(&buf, 72, 72, "EWF1 section descriptor")?;
2739        let desc = ewf1::SectionDescriptor::parse(&buf, offset)?;
2740        let data_size = desc.data_size()?;
2741        let data_offset = descriptor_end;
2742        let data_end = data_offset
2743            .checked_add(data_size)
2744            .ok_or_else(|| EwfError::Malformed("EWF1 section data exceeds file".into()))?;
2745        if data_end > file_len {
2746            return Err(EwfError::Malformed("EWF1 section data exceeds file".into()));
2747        }
2748        let section_type = desc.section_type.clone();
2749        let next = desc.next;
2750        sections.push(Section {
2751            desc,
2752            data_offset,
2753            data_size,
2754        });
2755        if matches!(section_type.as_str(), "done" | "next") || next == 0 {
2756            return Ok(sections);
2757        }
2758        if next <= offset {
2759            return Err(EwfError::Malformed(
2760                "EWF1 section chain does not advance".into(),
2761            ));
2762        }
2763        if next < data_end {
2764            return Err(EwfError::Malformed(
2765                "EWF1 next section offset overlaps current section".into(),
2766            ));
2767        }
2768        offset = next;
2769    }
2770}
2771
2772fn ewf1_acquisition_complete(sections: &[Section]) -> bool {
2773    sections
2774        .last()
2775        .is_none_or(|section| section.desc.section_type != "next")
2776}
2777
2778fn scan_ewf2_sections(
2779    file: &mut dyn SegmentReader,
2780    strictness: OpenStrictness,
2781) -> Result<Vec<Ewf2Section>> {
2782    let file_len = file.segment_len()?;
2783    if file_len < ewf2::FILE_HEADER_SIZE as u64 + ewf2::SECTION_DESCRIPTOR_SIZE as u64 {
2784        return Err(EwfError::Malformed("EWF2 file is too short".into()));
2785    }
2786
2787    if let Some(sections) = scan_ewf2_leading_sections(file, file_len, strictness)? {
2788        return Ok(sections);
2789    }
2790    scan_ewf2_trailing_sections(file, file_len, strictness)
2791}
2792
2793fn scan_ewf2_leading_sections(
2794    file: &mut dyn SegmentReader,
2795    file_len: u64,
2796    strictness: OpenStrictness,
2797) -> Result<Option<Vec<Ewf2Section>>> {
2798    let mut sections = Vec::new();
2799    let mut offset = ewf2::FILE_HEADER_SIZE as u64;
2800
2801    loop {
2802        if offset
2803            .checked_add(ewf2::SECTION_DESCRIPTOR_SIZE as u64)
2804            .is_none_or(|end| end > file_len)
2805        {
2806            if sections.is_empty() {
2807                return Ok(None);
2808            }
2809            return Err(EwfError::Malformed(
2810                "EWF2 leading section descriptor exceeds file".into(),
2811            ));
2812        }
2813
2814        let mut buf = [0; ewf2::SECTION_DESCRIPTOR_SIZE];
2815        file.seek(SeekFrom::Start(offset))?;
2816        file.read_exact(&mut buf)?;
2817        let Ok(desc) = ewf2::SectionDescriptor::parse(&buf, offset) else {
2818            return if sections.is_empty() {
2819                Ok(None)
2820            } else {
2821                Err(EwfError::Malformed(
2822                    "EWF2 leading section descriptor is invalid".into(),
2823                ))
2824            };
2825        };
2826        if !is_valid_ewf2_leading_descriptor(desc, strictness) {
2827            return if sections.is_empty() {
2828                Ok(None)
2829            } else {
2830                Err(EwfError::Malformed(
2831                    "EWF2 leading section descriptor is invalid".into(),
2832                ))
2833            };
2834        }
2835        validate_present_adler32_checksum(&buf, 60, 60, "EWF2 section descriptor")?;
2836
2837        let data_offset = offset
2838            .checked_add(u64::from(desc.descriptor_size))
2839            .ok_or_else(|| EwfError::Malformed("EWF2 section data offset overflow".into()))?;
2840        let data_end = data_offset
2841            .checked_add(desc.data_size)
2842            .ok_or_else(|| EwfError::Malformed("EWF2 section advance overflow".into()))?;
2843        if data_end > file_len {
2844            return if sections.is_empty() {
2845                Ok(None)
2846            } else {
2847                Err(EwfError::Malformed("EWF2 section data exceeds file".into()))
2848            };
2849        }
2850        let padding_size = ewf2_section_padding_size(desc)?;
2851        let next_offset = data_end
2852            .checked_add(padding_size)
2853            .ok_or_else(|| EwfError::Malformed("EWF2 section padding overflow".into()))?;
2854        if next_offset > file_len {
2855            return Err(EwfError::Malformed(
2856                "EWF2 section padding exceeds file".into(),
2857            ));
2858        }
2859        reject_encrypted_ewf2_section(desc)?;
2860        validate_ewf2_section_integrity_hash(file, desc, data_offset)?;
2861        let section_type = desc.section_type;
2862        sections.push(Ewf2Section {
2863            desc,
2864            data_offset,
2865            data_size: desc.data_size,
2866            layout: Ewf2SectionLayout::LeadingDescriptor,
2867        });
2868        if is_terminal_ewf2_section(section_type) {
2869            return Ok(Some(sections));
2870        }
2871        if next_offset <= offset {
2872            return Err(EwfError::Malformed(
2873                "EWF2 leading section chain does not advance".into(),
2874            ));
2875        }
2876        offset = next_offset;
2877    }
2878}
2879
2880fn scan_ewf2_trailing_sections(
2881    file: &mut dyn SegmentReader,
2882    file_len: u64,
2883    strictness: OpenStrictness,
2884) -> Result<Vec<Ewf2Section>> {
2885    let mut sections = Vec::new();
2886    let header_size = ewf2::FILE_HEADER_SIZE as u64;
2887    let descriptor_size = ewf2::SECTION_DESCRIPTOR_SIZE as u64;
2888    let mut offset = file_len
2889        .checked_sub(descriptor_size)
2890        .ok_or_else(|| EwfError::Malformed("EWF2 file is too short".into()))?;
2891    let max_sections = ((file_len - header_size) / descriptor_size).saturating_add(1);
2892
2893    for _ in 0..max_sections {
2894        let mut buf = [0; ewf2::SECTION_DESCRIPTOR_SIZE];
2895        file.seek(SeekFrom::Start(offset))?;
2896        file.read_exact(&mut buf)?;
2897        let desc = ewf2::SectionDescriptor::parse(&buf, offset)?;
2898        if !is_valid_ewf2_descriptor(desc, strictness) {
2899            return Err(EwfError::Malformed(
2900                "EWF2 trailing section descriptor is invalid".into(),
2901            ));
2902        }
2903        validate_present_adler32_checksum(&buf, 60, 60, "EWF2 section descriptor")?;
2904        ewf2_section_padding_size(desc)?;
2905        let data_offset = if desc.previous_offset == 0 {
2906            header_size
2907        } else {
2908            desc.previous_offset
2909                .checked_add(descriptor_size)
2910                .ok_or_else(|| {
2911                    EwfError::Malformed("EWF2 previous section offset overflow".into())
2912                })?
2913        };
2914        if data_offset < header_size {
2915            return Err(EwfError::Malformed(
2916                "EWF2 trailing section data precedes file header".into(),
2917            ));
2918        }
2919        let data_end = data_offset
2920            .checked_add(desc.data_size)
2921            .ok_or_else(|| EwfError::Malformed("EWF2 trailing section data overflow".into()))?;
2922        if data_end > offset {
2923            return Err(EwfError::Malformed(
2924                "EWF2 trailing section data exceeds descriptor".into(),
2925            ));
2926        }
2927        if desc.previous_offset != 0 {
2928            if desc.previous_offset >= desc.offset {
2929                return Err(EwfError::Malformed(
2930                    "EWF2 previous section offset is not before current section".into(),
2931                ));
2932            }
2933            let previous_end = desc
2934                .previous_offset
2935                .checked_add(descriptor_size)
2936                .ok_or_else(|| {
2937                    EwfError::Malformed("EWF2 previous section offset overflow".into())
2938                })?;
2939            if previous_end > offset {
2940                return Err(EwfError::Malformed(
2941                    "EWF2 previous section overlaps current section descriptor".into(),
2942                ));
2943            }
2944        }
2945
2946        reject_encrypted_ewf2_section(desc)?;
2947        validate_ewf2_section_integrity_hash(file, desc, data_offset)?;
2948        let previous_offset = desc.previous_offset;
2949        sections.push(Ewf2Section {
2950            desc,
2951            data_offset,
2952            data_size: desc.data_size,
2953            layout: Ewf2SectionLayout::TrailingDescriptor,
2954        });
2955        if previous_offset == 0 {
2956            sections.reverse();
2957            return Ok(sections);
2958        }
2959        offset = previous_offset;
2960    }
2961
2962    Err(EwfError::Malformed(
2963        "EWF2 trailing section descriptor chain is too long".into(),
2964    ))
2965}
2966
2967fn is_valid_ewf2_descriptor(desc: ewf2::SectionDescriptor, strictness: OpenStrictness) -> bool {
2968    desc.descriptor_size == ewf2::SECTION_DESCRIPTOR_SIZE as u32
2969        && (strictness == OpenStrictness::Lenient
2970            || !matches!(desc.section_type, ewf2::SectionType::Unknown(_)))
2971}
2972
2973fn is_valid_ewf2_leading_descriptor(
2974    desc: ewf2::SectionDescriptor,
2975    strictness: OpenStrictness,
2976) -> bool {
2977    is_valid_ewf2_descriptor(desc, strictness)
2978}
2979
2980fn ewf2_section_padding_size(desc: ewf2::SectionDescriptor) -> Result<u64> {
2981    let padding_size = u64::from(desc.padding_size);
2982    if padding_size > desc.data_size {
2983        return Err(EwfError::Malformed(
2984            "EWF2 section padding size exceeds data size".into(),
2985        ));
2986    }
2987    Ok(padding_size)
2988}
2989
2990fn reject_encrypted_ewf2_section(desc: ewf2::SectionDescriptor) -> Result<()> {
2991    if desc.section_type == ewf2::SectionType::EncryptionKeys {
2992        return Err(EwfError::Unsupported(
2993            "encrypted EWF2 image with encryption keys section".into(),
2994        ));
2995    }
2996    if desc.encrypted {
2997        return Err(EwfError::Unsupported(format!(
2998            "encrypted EWF2 {:?} section",
2999            desc.section_type
3000        )));
3001    }
3002    Ok(())
3003}
3004
3005fn validate_ewf2_section_integrity_hash(
3006    file: &mut dyn SegmentReader,
3007    desc: ewf2::SectionDescriptor,
3008    data_offset: u64,
3009) -> Result<()> {
3010    if !desc.has_integrity_hash {
3011        return Ok(());
3012    }
3013
3014    let mut hasher = md5::Md5::new();
3015    let mut remaining = desc.data_size;
3016    let mut buffer = [0; 8192];
3017    file.seek(SeekFrom::Start(data_offset))?;
3018    while remaining > 0 {
3019        let take = usize::try_from(remaining.min(buffer.len() as u64))
3020            .expect("section hash read is bounded by buffer length");
3021        file.read_exact(&mut buffer[..take])?;
3022        md5::Digest::update(&mut hasher, &buffer[..take]);
3023        remaining -= u64::try_from(take).expect("usize fits u64");
3024    }
3025
3026    let calculated: [u8; 16] = hasher.finalize().into();
3027    if calculated != desc.data_integrity_hash {
3028        return Err(EwfError::Malformed(
3029            "EWF2 section data integrity hash mismatch".into(),
3030        ));
3031    }
3032
3033    Ok(())
3034}
3035
3036fn remember_ewf2_metadata_payload(
3037    target: &mut Option<Vec<u8>>,
3038    data: &[u8],
3039    label: &str,
3040) -> Result<()> {
3041    if let Some(existing) = target {
3042        if existing.as_slice() != data {
3043            return Err(EwfError::Malformed(format!("EWF2 {label} does not match")));
3044        }
3045    } else {
3046        *target = Some(data.to_vec());
3047    }
3048    Ok(())
3049}
3050
3051fn is_terminal_ewf2_section(section_type: ewf2::SectionType) -> bool {
3052    matches!(
3053        section_type,
3054        ewf2::SectionType::Done | ewf2::SectionType::Next
3055    )
3056}
3057
3058fn ewf2_acquisition_complete(sections: &[Ewf2Section]) -> bool {
3059    sections
3060        .last()
3061        .is_none_or(|section| section.desc.section_type != ewf2::SectionType::Next)
3062}
3063
3064fn parse_ewf2_memory_extents_table(data: &[u8]) -> Result<Vec<MemoryExtent>> {
3065    const ENTRY_SIZE: usize = 16;
3066
3067    if !data.len().is_multiple_of(ENTRY_SIZE) {
3068        return Err(EwfError::Malformed(
3069            "EWF2 memory extents table has partial entry".into(),
3070        ));
3071    }
3072
3073    Ok(data
3074        .chunks_exact(ENTRY_SIZE)
3075        .map(|entry| MemoryExtent {
3076            start_page: u64::from_le_bytes(entry[0..8].try_into().expect("slice length checked")),
3077            page_count: u64::from_le_bytes(entry[8..16].try_into().expect("slice length checked")),
3078        })
3079        .collect())
3080}
3081
3082fn parse_ewf1_ltree_data(data: &[u8]) -> Result<SingleFilesInfo> {
3083    if data.len() < EWF1_LTREE_HEADER_SIZE {
3084        return Err(EwfError::Malformed(
3085            "EWF1 ltree section is too short".into(),
3086        ));
3087    }
3088
3089    let single_files_data_size =
3090        u64::from_le_bytes(data[16..24].try_into().expect("ltree header size checked"));
3091    let single_files_data_size = usize::try_from(single_files_data_size)
3092        .map_err(|_| EwfError::Malformed("EWF1 ltree data size does not fit usize".into()))?;
3093    let single_files_data_end = EWF1_LTREE_HEADER_SIZE
3094        .checked_add(single_files_data_size)
3095        .ok_or_else(|| EwfError::Malformed("EWF1 ltree data size overflow".into()))?;
3096    if single_files_data_end > data.len() {
3097        return Err(EwfError::Malformed(
3098            "EWF1 ltree data size exceeds section".into(),
3099        ));
3100    }
3101
3102    let stored = u32::from_le_bytes(data[24..28].try_into().expect("ltree header size checked"));
3103    let mut header = data[..EWF1_LTREE_HEADER_SIZE].to_vec();
3104    header[24..28].fill(0);
3105    validate_adler32_checksum_value(stored, &header, "EWF1 ltree header")?;
3106
3107    parse_ewf2_single_files_data(&data[EWF1_LTREE_HEADER_SIZE..single_files_data_end])
3108}
3109
3110fn parse_ewf2_single_files_aux_u64_table(data: &[u8], label: &str) -> Result<Vec<u64>> {
3111    parse_ewf2_single_files_aux_table(data, 8, label, |entry| {
3112        u64::from_le_bytes(entry.try_into().expect("entry size checked"))
3113    })
3114}
3115
3116fn parse_ewf2_single_files_md5_hash_table(data: &[u8]) -> Result<Vec<[u8; 16]>> {
3117    parse_ewf2_single_files_aux_table(data, 16, "EWF2 single files MD5 hash table", |entry| {
3118        entry.try_into().expect("entry size checked")
3119    })
3120}
3121
3122fn parse_ewf2_single_files_aux_table<T>(
3123    data: &[u8],
3124    entry_size: usize,
3125    label: &str,
3126    parse_entry: impl Fn(&[u8]) -> T,
3127) -> Result<Vec<T>> {
3128    const PADDED_HEADER_SIZE: usize = 32;
3129    const FOOTER_SIZE: usize = 4;
3130
3131    if data.len() < PADDED_HEADER_SIZE + FOOTER_SIZE {
3132        return Err(EwfError::Malformed(format!("{label} is too short")));
3133    }
3134
3135    validate_present_adler32_checksum(data, 16, 16, &format!("{label} header"))?;
3136    let entry_count = u32::from_le_bytes(data[0..4].try_into().expect("slice length checked"));
3137    let entry_bytes = usize::try_from(entry_count)
3138        .map_err(|_| EwfError::Malformed(format!("{label} entry count does not fit usize")))?
3139        .checked_mul(entry_size)
3140        .ok_or_else(|| EwfError::Malformed(format!("{label} entry bytes overflow")))?;
3141    let entries_offset = PADDED_HEADER_SIZE;
3142    let entries_end = entries_offset
3143        .checked_add(entry_bytes)
3144        .ok_or_else(|| EwfError::Malformed(format!("{label} entry range overflow")))?;
3145    let footer_end = entries_end
3146        .checked_add(FOOTER_SIZE)
3147        .ok_or_else(|| EwfError::Malformed(format!("{label} footer range overflow")))?;
3148    if footer_end > data.len() {
3149        return Err(EwfError::Malformed(format!(
3150            "{label} entries exceed section"
3151        )));
3152    }
3153
3154    let stored = u32::from_le_bytes(
3155        data[entries_end..entries_end + FOOTER_SIZE]
3156            .try_into()
3157            .expect("footer range checked"),
3158    );
3159    if stored != 0 {
3160        validate_adler32_checksum_value(stored, &data[entries_offset..entries_end], label)?;
3161    }
3162    Ok(data[entries_offset..entries_end]
3163        .chunks_exact(entry_size)
3164        .map(parse_entry)
3165        .collect())
3166}
3167
3168fn parse_ewf1_ranges(
3169    file: &mut dyn SegmentReader,
3170    sections: &[Section],
3171    segment_index: usize,
3172    first_chunk: u64,
3173    logical_size: u64,
3174    allow_large_compressed_chunks: bool,
3175    statistics: &ReaderStatisticsCollector,
3176) -> Result<Vec<TableRange>> {
3177    let mut ranges = Vec::new();
3178    let mut next_chunk = first_chunk;
3179    let mut previous_table: Option<(bool, u32, u64)> = None;
3180    for section in sections
3181        .iter()
3182        .filter(|section| matches!(section.desc.section_type.as_str(), "table" | "table2"))
3183    {
3184        if section.data_size < 24 {
3185            return Err(EwfError::Malformed(
3186                "EWF1 table section is too short".into(),
3187            ));
3188        }
3189        let data = read_exact_at(file, section.data_offset, 24)?;
3190        validate_present_adler32_checksum(&data, 20, 20, "EWF1 table header")?;
3191        let entry_count = u32::from_le_bytes(data[0..4].try_into().expect("slice length checked"));
3192        let base_offset = u64::from_le_bytes(data[8..16].try_into().expect("slice length checked"));
3193        if entry_count == 0 {
3194            continue;
3195        }
3196        if section.desc.section_type == "table2"
3197            && previous_table.is_some_and(|(previous_was_table, previous_count, previous_base)| {
3198                previous_was_table && previous_count == entry_count && previous_base == base_offset
3199            })
3200        {
3201            continue;
3202        }
3203
3204        let entry_bytes = u64::from(entry_count)
3205            .checked_mul(4)
3206            .ok_or_else(|| EwfError::Malformed("EWF1 table entry bytes overflow".into()))?;
3207        let entries_offset = section
3208            .data_offset
3209            .checked_add(24)
3210            .ok_or_else(|| EwfError::Malformed("EWF1 table entry offset overflow".into()))?;
3211        let entries_end = entries_offset
3212            .checked_add(entry_bytes)
3213            .ok_or_else(|| EwfError::Malformed("EWF1 table entry range overflow".into()))?;
3214        let section_end = section
3215            .data_offset
3216            .checked_add(section.data_size)
3217            .ok_or_else(|| EwfError::Malformed("EWF1 table section range overflow".into()))?;
3218        if entries_end > section_end {
3219            return Err(EwfError::Malformed(
3220                "EWF1 table entries exceed section".into(),
3221            ));
3222        }
3223        let first_entry_offset = if base_offset == 0 {
3224            let raw = read_exact_at(file, entries_offset, 4)?;
3225            Some(u64::from(
3226                u32::from_le_bytes(raw.try_into().expect("first table entry read size checked"))
3227                    & 0x7fff_ffff,
3228            ))
3229        } else {
3230            None
3231        };
3232        let table_resident_without_entries_checksum =
3233            first_entry_offset.is_some_and(|offset| offset == entries_end);
3234        if entries_end
3235            .checked_add(4)
3236            .is_some_and(|footer_end| footer_end <= section_end)
3237            && !table_resident_without_entries_checksum
3238        {
3239            validate_present_table_entries_checksum(
3240                file,
3241                entries_offset,
3242                entry_bytes,
3243                entries_end,
3244                "EWF1 table entries",
3245                statistics,
3246            )?;
3247        }
3248
3249        let (data_base, data_end) = if let Some(sectors) =
3250            matching_sectors_section(sections, base_offset).or_else(|| {
3251                first_entry_offset
3252                    .and_then(|offset| sectors_section_containing_offset(sections, offset))
3253            }) {
3254            let data_end = sectors
3255                .data_offset
3256                .checked_add(sectors.data_size)
3257                .ok_or_else(|| EwfError::Malformed("EWF1 sectors range overflow".into()))?;
3258            (base_offset, data_end)
3259        } else {
3260            let resident_data_start = if table_resident_without_entries_checksum {
3261                entries_end
3262            } else {
3263                entries_end.checked_add(4).ok_or_else(|| {
3264                    EwfError::Malformed("EWF1 table-resident data offset overflow".into())
3265                })?
3266            };
3267            if resident_data_start > section_end {
3268                return Err(EwfError::Malformed(
3269                    "EWF1 table-resident data starts beyond table section".into(),
3270                ));
3271            }
3272            (0, section_end)
3273        };
3274
3275        ranges.push(TableRange {
3276            kind: TableRangeKind::Ewf1,
3277            segment_index,
3278            first_chunk: next_chunk,
3279            chunk_count: u64::from(entry_count),
3280            entries_offset,
3281            base_offset: data_base,
3282            data_end: Some(data_end),
3283            ewf1_allow_large_compressed_chunks: allow_large_compressed_chunks,
3284            ewf2_compression_method: None,
3285        });
3286        next_chunk = next_chunk
3287            .checked_add(u64::from(entry_count))
3288            .ok_or_else(|| EwfError::Malformed("EWF1 table chunk count overflow".into()))?;
3289        previous_table = Some((
3290            section.desc.section_type == "table",
3291            entry_count,
3292            base_offset,
3293        ));
3294    }
3295
3296    if ranges.is_empty() && logical_size > 0 {
3297        return Err(EwfError::Malformed("EWF1 table coverage is missing".into()));
3298    }
3299    Ok(ranges)
3300}
3301
3302fn matching_sectors_section(sections: &[Section], base_offset: u64) -> Option<&Section> {
3303    sections
3304        .iter()
3305        .filter(|section| section.desc.section_type == "sectors")
3306        .find(|section| {
3307            base_offset == section.desc.offset
3308                || sectors_section_contains_offset(section, base_offset)
3309        })
3310}
3311
3312fn sectors_section_containing_offset(sections: &[Section], offset: u64) -> Option<&Section> {
3313    sections
3314        .iter()
3315        .filter(|section| section.desc.section_type == "sectors")
3316        .find(|section| sectors_section_contains_offset(section, offset))
3317}
3318
3319fn sectors_section_contains_offset(section: &Section, offset: u64) -> bool {
3320    section
3321        .data_offset
3322        .checked_add(section.data_size)
3323        .is_some_and(|data_end| offset >= section.data_offset && offset <= data_end)
3324}
3325
3326fn parse_ewf2_ranges(
3327    file: &mut dyn SegmentReader,
3328    sections: &[Ewf2Section],
3329    segment_index: usize,
3330    logical_size: u64,
3331    compression_method: ewf2::CompressionMethod,
3332    statistics: &ReaderStatisticsCollector,
3333) -> Result<Vec<TableRange>> {
3334    let mut ranges = Vec::new();
3335    for section in sections
3336        .iter()
3337        .filter(|section| section.desc.section_type == ewf2::SectionType::SectorTable)
3338    {
3339        let minimum_table_header_size = match section.layout {
3340            Ewf2SectionLayout::LeadingDescriptor => ewf2::TABLE_HEADER_SIZE as u64,
3341            Ewf2SectionLayout::TrailingDescriptor => EWF2_TABLE_HEADER_V2_SIZE,
3342        };
3343        if section.data_size < minimum_table_header_size {
3344            return Err(EwfError::Malformed("EWF2 sector table is too short".into()));
3345        }
3346        let header_read_size = if section.data_size >= EWF2_TABLE_HEADER_V2_SIZE {
3347            EWF2_TABLE_HEADER_V2_SIZE
3348        } else {
3349            minimum_table_header_size
3350        };
3351        let data = read_exact_at(file, section.data_offset, header_read_size)?;
3352        validate_present_adler32_checksum(&data, 16, 16, "EWF2 table header")?;
3353        let header = ewf2::TableHeader::parse(&data[..ewf2::TABLE_HEADER_SIZE])?;
3354        let entry_count = u64::from(header.entry_count);
3355        if entry_count == 0 {
3356            continue;
3357        }
3358        let entries_bytes = entry_count
3359            .checked_mul(ewf2::TABLE_ENTRY_SIZE as u64)
3360            .ok_or_else(|| EwfError::Malformed("EWF2 table entry bytes overflow".into()))?;
3361        let table_header_and_padding_size = match section.layout {
3362            Ewf2SectionLayout::TrailingDescriptor => EWF2_TABLE_HEADER_V2_SIZE,
3363            Ewf2SectionLayout::LeadingDescriptor => {
3364                let full_header_entries_size = EWF2_TABLE_HEADER_V2_SIZE
3365                    .checked_add(entries_bytes)
3366                    .ok_or_else(|| EwfError::Malformed("EWF2 table size overflow".into()))?;
3367                if section.data_size >= full_header_entries_size {
3368                    EWF2_TABLE_HEADER_V2_SIZE
3369                } else {
3370                    ewf2::TABLE_HEADER_SIZE as u64
3371                }
3372            }
3373        };
3374        let entries_offset = section
3375            .data_offset
3376            .checked_add(table_header_and_padding_size)
3377            .ok_or_else(|| EwfError::Malformed("EWF2 table entry offset overflow".into()))?;
3378        let entries_end = entries_offset
3379            .checked_add(entries_bytes)
3380            .ok_or_else(|| EwfError::Malformed("EWF2 table entry range overflow".into()))?;
3381        let section_end = section
3382            .data_offset
3383            .checked_add(section.data_size)
3384            .ok_or_else(|| EwfError::Malformed("EWF2 table section range overflow".into()))?;
3385        if entries_end > section_end {
3386            return Err(EwfError::Malformed(
3387                "EWF2 table entries exceed section".into(),
3388            ));
3389        }
3390        if entries_end
3391            .checked_add(EWF2_TABLE_FOOTER_SIZE)
3392            .is_some_and(|footer_end| footer_end <= section_end)
3393        {
3394            validate_present_table_entries_checksum(
3395                file,
3396                entries_offset,
3397                entries_bytes,
3398                entries_end,
3399                "EWF2 table entries",
3400                statistics,
3401            )?;
3402        }
3403
3404        ranges.push(TableRange {
3405            kind: TableRangeKind::Ewf2,
3406            segment_index,
3407            first_chunk: header.first_chunk,
3408            chunk_count: entry_count,
3409            entries_offset,
3410            base_offset: 0,
3411            data_end: None,
3412            ewf1_allow_large_compressed_chunks: false,
3413            ewf2_compression_method: Some(compression_method_code(compression_method)),
3414        });
3415    }
3416
3417    if ranges.is_empty() && logical_size > 0 {
3418        return Err(EwfError::Malformed("EWF2 image has no sector table".into()));
3419    }
3420    Ok(ranges)
3421}
3422
3423fn read_exact_at(file: &mut dyn SegmentReader, offset: u64, size: u64) -> Result<Vec<u8>> {
3424    let mut data = vec![
3425        0;
3426        usize::try_from(size).map_err(|_| {
3427            EwfError::Malformed("read size does not fit usize".into())
3428        })?
3429    ];
3430    file.seek(SeekFrom::Start(offset))?;
3431    file.read_exact(&mut data)?;
3432    Ok(data)
3433}
3434
3435fn table_entry_offset(range: &TableRange, local_index: u64, entry_size: u64) -> Result<u64> {
3436    range
3437        .entries_offset
3438        .checked_add(
3439            local_index
3440                .checked_mul(entry_size)
3441                .ok_or_else(|| EwfError::Malformed("table entry offset overflow".into()))?,
3442        )
3443        .ok_or_else(|| EwfError::Malformed("table entry offset overflow".into()))
3444}
3445
3446fn validate_ewf1_encoded_size(
3447    encoded_size: u64,
3448    chunk_size: u64,
3449    encoding: ChunkEncoding,
3450    allow_large_compressed_chunks: bool,
3451) -> Result<()> {
3452    if encoding == ChunkEncoding::Zlib {
3453        let standard_maximum = zlib_compressed_chunk_size_cap(chunk_size)?;
3454        if !allow_large_compressed_chunks && encoded_size <= standard_maximum {
3455            return validate_encoded_size(encoded_size, chunk_size, encoding);
3456        }
3457        if encoded_size == 0 {
3458            return Err(EwfError::Malformed("chunk data size is zero".into()));
3459        }
3460        let maximum = chunk_size
3461            .checked_mul(2)
3462            .ok_or_else(|| EwfError::Malformed("EWF1 chunk size cap overflow".into()))?;
3463        if encoded_size > maximum {
3464            return Err(EwfError::Malformed(format!(
3465                "EWF1 compressed chunk size {encoded_size} exceeds maximum {maximum}"
3466            )));
3467        }
3468        Ok(())
3469    } else {
3470        validate_encoded_size(encoded_size, chunk_size, encoding)
3471    }
3472}
3473
3474fn ewf1_chunk_encoding(
3475    entry_compressed: bool,
3476    encoded_size: u64,
3477    chunk_size: u64,
3478) -> Result<ChunkEncoding> {
3479    if entry_compressed {
3480        return Ok(ChunkEncoding::Zlib);
3481    }
3482    if encoded_size > raw_chunk_size_cap(chunk_size)?
3483        && encoded_size <= zlib_compressed_chunk_size_cap(chunk_size)?
3484    {
3485        return Ok(ChunkEncoding::Zlib);
3486    }
3487    Ok(ChunkEncoding::Raw)
3488}
3489
3490fn decode_ewf1_entry(
3491    range: &TableRange,
3492    raw: u32,
3493    chunk_size: u64,
3494    next_raw: Option<u32>,
3495    is_final: bool,
3496) -> Result<Ewf1DecodedEntry> {
3497    let entry = ewf1::TableEntry::parse(&raw.to_le_bytes())?;
3498    let masked_offset = range
3499        .base_offset
3500        .checked_add(entry.offset)
3501        .ok_or_else(|| EwfError::Malformed("EWF1 chunk offset overflow".into()))?;
3502
3503    if should_use_full_width_ewf1_offset(range, raw, masked_offset, chunk_size, next_raw, is_final)?
3504    {
3505        return Ok(Ewf1DecodedEntry {
3506            compressed: false,
3507            offset: range
3508                .base_offset
3509                .checked_add(u64::from(raw))
3510                .ok_or_else(|| EwfError::Malformed("EWF1 chunk offset overflow".into()))?,
3511        });
3512    }
3513
3514    Ok(Ewf1DecodedEntry {
3515        compressed: entry.compressed,
3516        offset: masked_offset,
3517    })
3518}
3519
3520fn should_use_full_width_ewf1_offset(
3521    range: &TableRange,
3522    raw: u32,
3523    masked_offset: u64,
3524    chunk_size: u64,
3525    next_raw: Option<u32>,
3526    is_final: bool,
3527) -> Result<bool> {
3528    if raw & 0x8000_0000 == 0 {
3529        return Ok(false);
3530    }
3531    let Some(data_end) = range.data_end else {
3532        return Ok(false);
3533    };
3534    let data_len = data_end
3535        .checked_sub(range.base_offset)
3536        .ok_or_else(|| EwfError::Malformed("EWF1 data region precedes base offset".into()))?;
3537    if data_len <= 0x8000_0000 {
3538        return Ok(false);
3539    }
3540
3541    let full_offset = range
3542        .base_offset
3543        .checked_add(u64::from(raw))
3544        .ok_or_else(|| EwfError::Malformed("EWF1 chunk offset overflow".into()))?;
3545    if full_offset >= data_end {
3546        return Ok(false);
3547    }
3548
3549    if let Some(next_raw) = next_raw {
3550        let next_masked = range
3551            .base_offset
3552            .checked_add(u64::from(next_raw & 0x7fff_ffff))
3553            .ok_or_else(|| EwfError::Malformed("EWF1 next chunk offset overflow".into()))?;
3554        let masked_is_valid = next_masked > masked_offset
3555            && next_masked - masked_offset <= zlib_compressed_chunk_size_cap(chunk_size)?;
3556        if masked_is_valid {
3557            return Ok(false);
3558        }
3559
3560        let next_full = if next_raw & 0x8000_0000 != 0 {
3561            range
3562                .base_offset
3563                .checked_add(u64::from(next_raw))
3564                .ok_or_else(|| EwfError::Malformed("EWF1 next chunk offset overflow".into()))?
3565        } else {
3566            next_masked
3567        };
3568        return Ok(
3569            next_full > full_offset && next_full - full_offset <= raw_chunk_size_cap(chunk_size)?
3570        );
3571    }
3572
3573    if is_final {
3574        let masked_size = data_end.saturating_sub(masked_offset);
3575        let full_size = data_end - full_offset;
3576        return Ok(masked_size > zlib_compressed_chunk_size_cap(chunk_size)?
3577            && full_size <= raw_chunk_size_cap(chunk_size)?);
3578    }
3579
3580    Ok(false)
3581}
3582
3583fn logical_chunk_size(logical_size: u64, chunk_size: u64, chunk_id: u64) -> Result<usize> {
3584    let logical_offset = chunk_id
3585        .checked_mul(chunk_size)
3586        .ok_or_else(|| EwfError::Malformed("logical chunk offset overflow".into()))?;
3587    let size = logical_size.saturating_sub(logical_offset).min(chunk_size);
3588    usize::try_from(size)
3589        .map_err(|_| EwfError::Malformed("logical chunk size does not fit usize".into()))
3590}
3591
3592fn checksum_error_range(
3593    info: &ImageInfo,
3594    chunk_id: u64,
3595    logical_size: usize,
3596) -> Result<SectorRange> {
3597    let logical_offset = chunk_id
3598        .checked_mul(info.chunk_size)
3599        .ok_or_else(|| EwfError::Malformed("checksum error logical offset overflow".into()))?;
3600    let logical_size = u64::try_from(logical_size)
3601        .map_err(|_| EwfError::Malformed("checksum error size does not fit u64".into()))?;
3602    let Some(bytes_per_sector) = info.media.bytes_per_sector.filter(|value| *value != 0) else {
3603        return Ok(SectorRange {
3604            first_sector: logical_offset,
3605            sector_count: logical_size.max(1),
3606        });
3607    };
3608
3609    Ok(SectorRange {
3610        first_sector: logical_offset / bytes_per_sector,
3611        sector_count: logical_size.div_ceil(bytes_per_sector).max(1),
3612    })
3613}
3614
3615fn validate_chunk_size(chunk_size: u64) -> Result<()> {
3616    if chunk_size == 0 {
3617        return Err(EwfError::Malformed("chunk size is zero".into()));
3618    }
3619    if chunk_size > MAX_CHUNK_SIZE {
3620        return Err(EwfError::Malformed(format!(
3621            "chunk size {chunk_size} exceeds maximum {MAX_CHUNK_SIZE}"
3622        )));
3623    }
3624    Ok(())
3625}
3626
3627fn apply_ewf2_geometry(media: &mut MediaInfo, geometry: Ewf2Geometry) {
3628    if let Some(value) = geometry.sectors_per_chunk {
3629        media.sectors_per_chunk = Some(value);
3630    }
3631    if let Some(value) = geometry.bytes_per_sector {
3632        media.bytes_per_sector = Some(value);
3633    }
3634    if let Some(value) = geometry.sector_count {
3635        media.sector_count = Some(value);
3636    }
3637    if let Some(value) = geometry.chunk_count {
3638        media.chunk_count = Some(value);
3639    }
3640    if let Some(value) = geometry.error_granularity {
3641        media.error_granularity = Some(value);
3642    }
3643    if let Some(value) = geometry.media_type {
3644        media.media_type = Some(value);
3645    }
3646    if let Some(value) = geometry.physical {
3647        media.media_flags.physical = value;
3648    }
3649    media.media_flags.fastbloc |= geometry.fastbloc;
3650    media.media_flags.tableau |= geometry.tableau;
3651}
3652
3653fn apply_ewf2_geometry_if_missing(media: &mut MediaInfo, geometry: Ewf2Geometry) {
3654    if media.sectors_per_chunk.is_none() {
3655        media.sectors_per_chunk = geometry.sectors_per_chunk;
3656    }
3657    if media.bytes_per_sector.is_none() {
3658        media.bytes_per_sector = geometry.bytes_per_sector;
3659    }
3660    if media.sector_count.is_none() {
3661        media.sector_count = geometry.sector_count;
3662    }
3663    if media.chunk_count.is_none() {
3664        media.chunk_count = geometry.chunk_count;
3665    }
3666    if media.error_granularity.is_none() {
3667        media.error_granularity = geometry.error_granularity;
3668    }
3669    if media.media_type.is_none() {
3670        media.media_type = geometry.media_type;
3671    }
3672    if let Some(value) = geometry.physical {
3673        media.media_flags.physical = value;
3674    }
3675    media.media_flags.fastbloc |= geometry.fastbloc;
3676    media.media_flags.tableau |= geometry.tableau;
3677}
3678
3679fn public_compression_method(method: ewf2::CompressionMethod) -> CompressionMethod {
3680    match method {
3681        ewf2::CompressionMethod::None => CompressionMethod::None,
3682        ewf2::CompressionMethod::Zlib => CompressionMethod::Zlib,
3683        ewf2::CompressionMethod::Bzip2 => CompressionMethod::Bzip2,
3684        ewf2::CompressionMethod::Unknown(method) => CompressionMethod::Unknown(method),
3685    }
3686}
3687
3688fn compression_method_code(method: ewf2::CompressionMethod) -> u16 {
3689    match method {
3690        ewf2::CompressionMethod::None => 0,
3691        ewf2::CompressionMethod::Zlib => 1,
3692        ewf2::CompressionMethod::Bzip2 => 2,
3693        ewf2::CompressionMethod::Unknown(method) => method,
3694    }
3695}
3696
3697fn merge_hashes(target: &mut StoredHashes, source: &StoredHashes) {
3698    if target.md5.is_none() {
3699        target.md5 = source.md5;
3700    }
3701    if target.sha1.is_none() {
3702        target.sha1 = source.sha1;
3703    }
3704    for (identifier, value) in &source.hash_values {
3705        target
3706            .hash_values
3707            .entry(identifier.clone())
3708            .or_insert_with(|| value.clone());
3709    }
3710}
3711
3712fn merge_segment_format_profile(
3713    target: &mut Option<FormatProfile>,
3714    target_hint_only: &mut bool,
3715    source: FormatProfile,
3716    source_hint_only: bool,
3717) -> Result<()> {
3718    if source == FormatProfile::Unknown {
3719        return Ok(());
3720    }
3721    match target {
3722        Some(existing) if *existing == FormatProfile::Unknown => {
3723            *existing = source;
3724            *target_hint_only = source_hint_only;
3725        }
3726        Some(existing) if *existing == source => {
3727            if !source_hint_only {
3728                *target_hint_only = false;
3729            }
3730        }
3731        Some(existing)
3732            if *target_hint_only
3733                && !source_hint_only
3734                && ewf1_profile_hint_matches_detected_profile(*existing, source) =>
3735        {
3736            *existing = source;
3737            *target_hint_only = false;
3738        }
3739        Some(existing)
3740            if !*target_hint_only
3741                && source_hint_only
3742                && ewf1_profile_hint_matches_detected_profile(source, *existing) => {}
3743        Some(existing) if *existing != source => {
3744            return Err(EwfError::Malformed(format!(
3745                "segment format profiles do not match ({existing:?} != {source:?})",
3746            )));
3747        }
3748        Some(_) => {}
3749        None => {
3750            *target = Some(source);
3751            *target_hint_only = source_hint_only;
3752        }
3753    }
3754    Ok(())
3755}
3756
3757fn ewf1_profile_hint_matches_detected_profile(
3758    hint: FormatProfile,
3759    detected: FormatProfile,
3760) -> bool {
3761    matches!(
3762        hint,
3763        FormatProfile::EnCase2 | FormatProfile::EnCase5 | FormatProfile::Ewf
3764    ) && matches!(
3765        detected,
3766        FormatProfile::EnCase1
3767            | FormatProfile::EnCase2
3768            | FormatProfile::EnCase3
3769            | FormatProfile::EnCase4
3770            | FormatProfile::EnCase5
3771            | FormatProfile::EnCase6
3772            | FormatProfile::EnCase7
3773            | FormatProfile::FtkImager
3774            | FormatProfile::Linen5
3775            | FormatProfile::Linen6
3776            | FormatProfile::Linen7
3777    )
3778}
3779
3780fn apply_detected_ewf1_format_profile(
3781    target: &mut FormatProfile,
3782    detected: Option<FormatProfile>,
3783) -> bool {
3784    let Some(detected) = detected.filter(|profile| *profile != FormatProfile::Unknown) else {
3785        return false;
3786    };
3787    if *target == FormatProfile::Smart && detected == FormatProfile::FtkImager {
3788        *target = detected;
3789        return true;
3790    }
3791    if matches!(
3792        target,
3793        FormatProfile::Smart
3794            | FormatProfile::LogicalEnCase5
3795            | FormatProfile::LogicalEnCase6
3796            | FormatProfile::LogicalEnCase7
3797    ) {
3798        return false;
3799    }
3800    *target = detected;
3801    true
3802}
3803
3804fn merge_single_files(
3805    target: &mut Option<SingleFilesInfo>,
3806    source: Option<SingleFilesInfo>,
3807) -> Result<()> {
3808    if let Some(source) = source {
3809        if target.is_some() {
3810            return Err(EwfError::Malformed(
3811                "EWF2 image has duplicate single files data sections".into(),
3812            ));
3813        }
3814        *target = Some(source);
3815    }
3816    Ok(())
3817}
3818
3819fn merge_single_files_aux_tables(
3820    target: &mut SingleFilesAuxTables,
3821    source: SingleFilesAuxTables,
3822) -> Result<()> {
3823    merge_single_files_aux_u64_table(
3824        &mut target.table_0x21_entries,
3825        source.table_0x21_entries,
3826        "0x21",
3827    )?;
3828    merge_single_files_aux_md5_table(&mut target.md5_hashes, source.md5_hashes)?;
3829    merge_single_files_aux_u64_table(
3830        &mut target.table_0x23_entries,
3831        source.table_0x23_entries,
3832        "0x23",
3833    )
3834}
3835
3836fn merge_single_files_aux_u64_table(
3837    target: &mut Vec<u64>,
3838    source: Vec<u64>,
3839    table_name: &str,
3840) -> Result<()> {
3841    if source.is_empty() {
3842        return Ok(());
3843    }
3844    if !target.is_empty() {
3845        return Err(EwfError::Malformed(format!(
3846            "EWF2 image has duplicate single files {table_name} table sections"
3847        )));
3848    }
3849    *target = source;
3850    Ok(())
3851}
3852
3853fn merge_single_files_aux_md5_table(
3854    target: &mut Vec<[u8; 16]>,
3855    source: Vec<[u8; 16]>,
3856) -> Result<()> {
3857    if source.is_empty() {
3858        return Ok(());
3859    }
3860    if !target.is_empty() {
3861        return Err(EwfError::Malformed(
3862            "EWF2 image has duplicate single files MD5 hash table sections".into(),
3863        ));
3864    }
3865    *target = source;
3866    Ok(())
3867}
3868
3869fn merge_optional_ewf2_string_section(
3870    target: &mut Option<String>,
3871    source: Option<String>,
3872    label: &str,
3873) -> Result<()> {
3874    let Some(source) = source else {
3875        return Ok(());
3876    };
3877    if target.is_some() {
3878        return Err(EwfError::Malformed(format!(
3879            "EWF2 image has duplicate {label} sections"
3880        )));
3881    }
3882    *target = Some(source);
3883    Ok(())
3884}
3885
3886fn merge_optional_ewf2_raw_section(
3887    target: &mut Option<Vec<u8>>,
3888    source: Option<Vec<u8>>,
3889    label: &str,
3890) -> Result<()> {
3891    let Some(source) = source else {
3892        return Ok(());
3893    };
3894    if target.is_some() {
3895        return Err(EwfError::Malformed(format!(
3896            "EWF2 image has duplicate {label} sections"
3897        )));
3898    }
3899    *target = Some(source);
3900    Ok(())
3901}
3902
3903fn parse_nonzero_hash<const N: usize>(data: &[u8]) -> Option<[u8; N]> {
3904    let value = data.get(..N)?;
3905    let mut hash = [0; N];
3906    hash.copy_from_slice(value);
3907    hash.iter().any(|byte| *byte != 0).then_some(hash)
3908}
3909
3910fn validate_adler32_checksum(
3911    data: &[u8],
3912    checksum_offset: usize,
3913    checksum_data_size: usize,
3914    label: &str,
3915) -> Result<()> {
3916    let stored = u32::from_le_bytes(
3917        data[checksum_offset..checksum_offset + 4]
3918            .try_into()
3919            .expect("hash section size checked"),
3920    );
3921    validate_adler32_checksum_value(stored, &data[..checksum_data_size], label)
3922}
3923
3924fn validate_present_adler32_checksum(
3925    data: &[u8],
3926    checksum_offset: usize,
3927    checksum_data_size: usize,
3928    label: &str,
3929) -> Result<()> {
3930    let stored = u32::from_le_bytes(
3931        data[checksum_offset..checksum_offset + 4]
3932            .try_into()
3933            .expect("checksum offset checked"),
3934    );
3935    if stored == 0 {
3936        return Ok(());
3937    }
3938    validate_adler32_checksum_value(stored, &data[..checksum_data_size], label)
3939}
3940
3941fn validate_present_table_entries_checksum(
3942    file: &mut dyn SegmentReader,
3943    entries_offset: u64,
3944    entry_bytes: u64,
3945    checksum_offset: u64,
3946    label: &str,
3947    statistics: &ReaderStatisticsCollector,
3948) -> Result<()> {
3949    let checksum = read_exact_at(file, checksum_offset, 4)?;
3950    let stored = u32::from_le_bytes(
3951        checksum
3952            .as_slice()
3953            .try_into()
3954            .expect("checksum size checked"),
3955    );
3956    if stored == 0 {
3957        return Ok(());
3958    }
3959    let started = statistics.enabled().then(Instant::now);
3960    let calculated = stream_adler32(file, entries_offset, entry_bytes)?;
3961    if let Some(started) = started {
3962        statistics.record_table_checksum(entry_bytes, started.elapsed());
3963    }
3964    if stored != calculated {
3965        return Err(EwfError::Malformed(format!("{label} checksum mismatch")));
3966    }
3967    Ok(())
3968}
3969
3970fn stream_adler32(file: &mut dyn SegmentReader, offset: u64, size: u64) -> Result<u32> {
3971    file.seek(SeekFrom::Start(offset))?;
3972    let mut remaining = size;
3973    let mut checksum = 1_u32;
3974    let mut buffer = vec![0_u8; TABLE_CHECKSUM_BUFFER_SIZE];
3975    while remaining > 0 {
3976        let take = usize::try_from(remaining.min(TABLE_CHECKSUM_BUFFER_SIZE as u64))
3977            .expect("table checksum read is bounded by the buffer");
3978        file.read_exact(&mut buffer[..take])?;
3979        checksum = adler32_update(checksum, &buffer[..take]);
3980        remaining -= u64::try_from(take).expect("usize fits u64");
3981    }
3982    Ok(checksum)
3983}
3984
3985fn validate_present_ewf1_media_checksum(data: &[u8], section_type: &str) -> Result<()> {
3986    let Some(checksum_offset) = data.len().checked_sub(4) else {
3987        return Ok(());
3988    };
3989    validate_present_adler32_checksum(
3990        data,
3991        checksum_offset,
3992        checksum_offset,
3993        &format!("EWF1 {section_type}"),
3994    )
3995}
3996
3997fn validate_adler32_checksum_value(stored: u32, checksum_data: &[u8], label: &str) -> Result<()> {
3998    let calculated = adler32(checksum_data);
3999    if stored != calculated {
4000        return Err(EwfError::Malformed(format!("{label} checksum mismatch")));
4001    }
4002    Ok(())
4003}
4004
4005fn validate_raw_chunk_checksum(encoded: &[u8], logical_size: usize) -> Result<()> {
4006    let checksum_offset = logical_size;
4007    if encoded.len() < checksum_offset + 4 {
4008        return Err(EwfError::Malformed(
4009            "raw chunk checksum trailer is missing".into(),
4010        ));
4011    }
4012    validate_adler32_checksum(encoded, checksum_offset, logical_size, "raw chunk")
4013}
4014
4015fn adler32(data: &[u8]) -> u32 {
4016    adler32_update(1, data)
4017}
4018
4019fn adler32_update(checksum: u32, data: &[u8]) -> u32 {
4020    const MOD_ADLER: u32 = 65_521;
4021    let mut a = checksum & 0xffff;
4022    let mut b = checksum >> 16;
4023    for byte in data {
4024        a = (a + u32::from(*byte)) % MOD_ADLER;
4025        b = (b + a) % MOD_ADLER;
4026    }
4027    (b << 16) | a
4028}
4029
4030fn insert_hash_value(stored_hashes: &mut StoredHashes, identifier: &str, hash: &[u8]) {
4031    stored_hashes
4032        .hash_values
4033        .entry(identifier.to_string())
4034        .or_insert_with(|| hex_string(hash));
4035}
4036
4037fn hex_string(bytes: &[u8]) -> String {
4038    const HEX: &[u8; 16] = b"0123456789abcdef";
4039    let mut out = String::with_capacity(bytes.len() * 2);
4040    for byte in bytes {
4041        out.push(char::from(HEX[(byte >> 4) as usize]));
4042        out.push(char::from(HEX[(byte & 0x0f) as usize]));
4043    }
4044    out
4045}
4046
4047fn validate_hash_section_size(observed: u64, expected: u64, label: &str) -> Result<()> {
4048    if observed != expected {
4049        return Err(EwfError::Malformed(format!(
4050            "{label} hash section has size {observed}, expected {expected}"
4051        )));
4052    }
4053    Ok(())
4054}
4055
4056fn validate_set_identifier(
4057    expected: &mut Option<[u8; 16]>,
4058    observed: Option<[u8; 16]>,
4059) -> Result<()> {
4060    let Some(observed) = observed else {
4061        return Ok(());
4062    };
4063    if let Some(expected) = expected {
4064        if *expected != observed {
4065            return Err(EwfError::Malformed(
4066                "segment set identifier mismatch".into(),
4067            ));
4068        }
4069    } else {
4070        *expected = Some(observed);
4071    }
4072    Ok(())
4073}
4074
4075fn validate_ewf2_header_profile(
4076    expected: &mut Option<Ewf2HeaderProfile>,
4077    observed: Option<Ewf2HeaderProfile>,
4078) -> Result<()> {
4079    let Some(observed) = observed else {
4080        return Ok(());
4081    };
4082    if let Some(expected) = expected {
4083        if expected.major_version != observed.major_version
4084            || expected.minor_version != observed.minor_version
4085        {
4086            return Err(EwfError::Malformed(
4087                "EWF2 segment file format version mismatch".into(),
4088            ));
4089        }
4090        if expected.compression_method != observed.compression_method {
4091            return Err(EwfError::Malformed(
4092                "EWF2 segment file compression method mismatch".into(),
4093            ));
4094        }
4095    } else {
4096        *expected = Some(observed);
4097    }
4098    Ok(())
4099}
4100
4101fn ewf1_metadata_payload(data: &[u8]) -> Vec<u8> {
4102    let mut decompressed = Vec::new();
4103    let result = flate2::read::ZlibDecoder::new(data)
4104        .take(MAX_DECOMPRESSED_METADATA + 1)
4105        .read_to_end(&mut decompressed);
4106    if result.is_ok() && decompressed.len() <= MAX_DECOMPRESSED_METADATA as usize {
4107        decompressed
4108    } else {
4109        data.to_vec()
4110    }
4111}
4112
4113fn ewf2_metadata_payload(
4114    data: &[u8],
4115    _compression_method: ewf2::CompressionMethod,
4116) -> Result<Vec<u8>> {
4117    if data.first() == Some(&0x78) {
4118        return decompress_ewf2_metadata(flate2::read::ZlibDecoder::new(data));
4119    }
4120    if data.starts_with(b"BZh") {
4121        return decompress_ewf2_metadata(bzip2::read::BzDecoder::new(data));
4122    }
4123    Ok(data.to_vec())
4124}
4125
4126fn decode_ewf2_string_section(
4127    data: &[u8],
4128    compression_method: ewf2::CompressionMethod,
4129    label: &str,
4130) -> Result<String> {
4131    let payload = ewf2_metadata_payload(data, compression_method)?;
4132    if payload.len() % 2 != 0 {
4133        return Err(EwfError::Malformed(format!(
4134            "EWF2 {label} section has odd UTF-16 size"
4135        )));
4136    }
4137
4138    let units: Vec<u16> = payload
4139        .chunks_exact(2)
4140        .map(|chunk| u16::from_le_bytes(chunk.try_into().expect("slice length checked")))
4141        .collect();
4142    let text = String::from_utf16(&units)
4143        .map_err(|_| EwfError::Malformed(format!("EWF2 {label} section is not valid UTF-16LE")))?;
4144    Ok(text.strip_prefix('\u{feff}').unwrap_or(&text).to_owned())
4145}
4146
4147fn decompress_ewf2_metadata(reader: impl Read) -> Result<Vec<u8>> {
4148    let mut decompressed = Vec::new();
4149    reader
4150        .take(MAX_DECOMPRESSED_METADATA + 1)
4151        .read_to_end(&mut decompressed)
4152        .map_err(|err| EwfError::Malformed(format!("EWF2 metadata decompression failed: {err}")))?;
4153    if decompressed.len() > MAX_DECOMPRESSED_METADATA as usize {
4154        return Err(EwfError::Malformed(
4155            "EWF2 metadata exceeds decompressed size limit".into(),
4156        ));
4157    }
4158    Ok(decompressed)
4159}
4160
4161#[cfg(test)]
4162mod tests {
4163    use std::io::{Cursor, Read, Seek, SeekFrom, Write};
4164    use std::sync::{
4165        Arc,
4166        atomic::{AtomicUsize, Ordering as AtomicOrdering},
4167    };
4168
4169    use flate2::write::ZlibEncoder;
4170
4171    use super::*;
4172
4173    struct ObservedReader {
4174        cursor: Cursor<Vec<u8>>,
4175        maximum_read: usize,
4176    }
4177
4178    impl Read for ObservedReader {
4179        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
4180            self.maximum_read = self.maximum_read.max(buffer.len());
4181            self.cursor.read(buffer)
4182        }
4183    }
4184
4185    impl Seek for ObservedReader {
4186        fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
4187            self.cursor.seek(position)
4188        }
4189    }
4190
4191    struct LengthObservedReader {
4192        cursor: Cursor<Vec<u8>>,
4193        seek_from_end_calls: Arc<AtomicUsize>,
4194    }
4195
4196    impl Read for LengthObservedReader {
4197        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
4198            self.cursor.read(buffer)
4199        }
4200    }
4201
4202    impl Seek for LengthObservedReader {
4203        fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
4204            if matches!(position, SeekFrom::End(_)) {
4205                self.seek_from_end_calls
4206                    .fetch_add(1, AtomicOrdering::Relaxed);
4207            }
4208            self.cursor.seek(position)
4209        }
4210    }
4211
4212    #[test]
4213    fn segment_file_pool_caches_segment_lengths() {
4214        let seek_from_end_calls = Arc::new(AtomicUsize::new(0));
4215        let reader = LengthObservedReader {
4216            cursor: Cursor::new(vec![0; 128]),
4217            seek_from_end_calls: Arc::clone(&seek_from_end_calls),
4218        };
4219        let mut pool = SegmentFilePool::new_readers(
4220            vec![Box::new(reader)],
4221            None,
4222            Arc::new(ReaderStatisticsCollector::new(false)),
4223        )
4224        .unwrap();
4225        let path = Path::new("cached-length.E01");
4226
4227        assert_eq!(pool.segment_len(0, path).unwrap(), 128);
4228        assert_eq!(pool.segment_len(0, path).unwrap(), 128);
4229        assert_eq!(seek_from_end_calls.load(AtomicOrdering::Relaxed), 1);
4230    }
4231
4232    #[test]
4233    fn table_checksum_validation_uses_bounded_reads() {
4234        const MAXIMUM_CHECKSUM_READ: usize = 64 * 1024;
4235        let entries = vec![0x5a; MAXIMUM_CHECKSUM_READ * 3 + 17];
4236        let mut bytes = entries.clone();
4237        bytes.extend_from_slice(&adler32(&entries).to_le_bytes());
4238        let mut reader = ObservedReader {
4239            cursor: Cursor::new(bytes),
4240            maximum_read: 0,
4241        };
4242
4243        validate_present_table_entries_checksum(
4244            &mut reader,
4245            0,
4246            entries.len() as u64,
4247            entries.len() as u64,
4248            "test table",
4249            &ReaderStatisticsCollector::new(false),
4250        )
4251        .unwrap();
4252
4253        assert!(reader.maximum_read <= MAXIMUM_CHECKSUM_READ);
4254    }
4255
4256    #[test]
4257    fn table_checksum_validation_rejects_mismatch_across_multiple_reads() {
4258        const CHECKSUM_READ_SIZE: usize = 64 * 1024;
4259        let entries = vec![0x3c; CHECKSUM_READ_SIZE * 2 + 7];
4260        let mut bytes = entries.clone();
4261        bytes.extend_from_slice(&adler32(b"different").to_le_bytes());
4262        let mut reader = Cursor::new(bytes);
4263
4264        let error = validate_present_table_entries_checksum(
4265            &mut reader,
4266            0,
4267            entries.len() as u64,
4268            entries.len() as u64,
4269            "test table",
4270            &ReaderStatisticsCollector::new(false),
4271        )
4272        .unwrap_err();
4273
4274        match error {
4275            EwfError::Malformed(message) => {
4276                assert_eq!(message, "test table checksum mismatch");
4277            }
4278            other => panic!("unexpected error: {other}"),
4279        }
4280    }
4281
4282    #[test]
4283    fn ewf1_metadata_payload_decompresses_zlib_data() {
4284        let mut encoder = ZlibEncoder::new(Vec::new(), flate2::Compression::default());
4285        encoder.write_all(b"metadata").unwrap();
4286        let compressed = encoder.finish().unwrap();
4287
4288        assert_eq!(ewf1_metadata_payload(&compressed), b"metadata");
4289    }
4290
4291    #[test]
4292    fn ewf1_metadata_payload_keeps_plain_data() {
4293        assert_eq!(ewf1_metadata_payload(b"plain metadata"), b"plain metadata");
4294    }
4295
4296    #[test]
4297    fn ewf2_metadata_payload_decompresses_zlib_data() {
4298        let mut encoder = ZlibEncoder::new(Vec::new(), flate2::Compression::default());
4299        encoder.write_all(b"metadata").unwrap();
4300        let compressed = encoder.finish().unwrap();
4301
4302        assert_eq!(
4303            ewf2_metadata_payload(&compressed, ewf2::CompressionMethod::Zlib).unwrap(),
4304            b"metadata"
4305        );
4306    }
4307}