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