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