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