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