Skip to main content

ewf_image/
types.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use crate::date_time::format_header_date_value;
6use crate::decode::{ChunkEncoding, decode_chunk};
7use crate::{EwfError, Result};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10/// Top-level EWF container generation.
11pub enum Format {
12    /// Original EWF/EVF/LVF segment format.
13    Ewf1,
14    /// EWF2/Ex01/Lx01 segment format.
15    Ewf2,
16}
17
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
19/// Producer/profile inferred from EWF metadata and section layout.
20pub enum FormatProfile {
21    /// The image profile could not be identified.
22    #[default]
23    Unknown,
24    /// `EnCase` version 1 style EWF1 image.
25    EnCase1,
26    /// `EnCase` version 2 style EWF1 image.
27    EnCase2,
28    /// `EnCase` version 3 style EWF1 image.
29    EnCase3,
30    /// `EnCase` version 4 style EWF1 image.
31    EnCase4,
32    /// `EnCase` version 5 style EWF1 image.
33    EnCase5,
34    /// `EnCase` version 6 style EWF1 image.
35    EnCase6,
36    /// `EnCase` version 7 style EWF1 image.
37    EnCase7,
38    /// SMART `.S01` style image.
39    Smart,
40    /// FTK Imager style EWF1 image.
41    FtkImager,
42    /// Linen version 5 style EWF1 image.
43    Linen5,
44    /// Linen version 6 style EWF1 image.
45    Linen6,
46    /// Linen version 7 style EWF1 image.
47    Linen7,
48    /// `EnCase` 5 logical `.L01` style image.
49    LogicalEnCase5,
50    /// `EnCase` 6 logical `.L01` style image.
51    LogicalEnCase6,
52    /// `EnCase` 7 logical `.L01` style image.
53    LogicalEnCase7,
54    /// `EnCase` 7 EWF2 physical `.Ex01` style image.
55    Ewf2EnCase7,
56    /// `EnCase` 7 EWF2 logical `.Lx01` style image.
57    Ewf2LogicalEnCase7,
58    /// Generic EWF-compatible image.
59    Ewf,
60    /// EWF extended profile.
61    Ewfx,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65/// Compression method recorded for stored chunks.
66pub enum CompressionMethod {
67    /// Chunks are stored without compression.
68    None,
69    /// Chunks use zlib compression.
70    Zlib,
71    /// Chunks use `BZip2` compression.
72    Bzip2,
73    /// An unrecognized on-disk compression method value.
74    Unknown(u16),
75}
76
77#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
78/// Compression level recorded in EWF metadata.
79pub enum CompressionLevel {
80    /// Producer default compression level.
81    Default,
82    /// No compression.
83    #[default]
84    None,
85    /// Fast compression.
86    Fast,
87    /// Best compression.
88    Best,
89    /// An unrecognized on-disk compression level value.
90    Unknown(i8),
91}
92
93impl CompressionLevel {
94    /// Returns the EWF metadata numeric representation for this level.
95    pub fn as_i8(self) -> i8 {
96        match self {
97            Self::Default => -1,
98            Self::None => 0,
99            Self::Fast => 1,
100            Self::Best => 2,
101            Self::Unknown(value) => value,
102        }
103    }
104
105    /// Converts an EWF metadata numeric compression level.
106    pub fn from_i8(value: i8) -> Self {
107        match value {
108            -1 => Self::Default,
109            0 => Self::None,
110            1 => Self::Fast,
111            2 => Self::Best,
112            value => Self::Unknown(value),
113        }
114    }
115}
116
117#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
118/// Compression flags recorded in EWF metadata.
119pub struct CompressionFlags {
120    /// Empty-block compression flag.
121    pub empty_block: bool,
122    /// Pattern-fill compression flag.
123    pub pattern_fill: bool,
124    /// Bits not recognized by this crate.
125    pub unknown_bits: u8,
126}
127
128impl CompressionFlags {
129    const EMPTY_BLOCK: u8 = 0x01;
130    const PATTERN_FILL: u8 = 0x10;
131    const KNOWN_BITS: u8 = Self::EMPTY_BLOCK | Self::PATTERN_FILL;
132
133    /// Decodes a raw EWF compression flags byte.
134    pub fn from_bits(bits: u8) -> Self {
135        Self {
136            empty_block: bits & Self::EMPTY_BLOCK != 0,
137            pattern_fill: bits & Self::PATTERN_FILL != 0,
138            unknown_bits: bits & !Self::KNOWN_BITS,
139        }
140    }
141
142    /// Encodes these flags as a raw EWF compression flags byte.
143    pub fn bits(self) -> u8 {
144        let mut bits = self.unknown_bits;
145        if self.empty_block {
146            bits |= Self::EMPTY_BLOCK;
147        }
148        if self.pattern_fill {
149            bits |= Self::PATTERN_FILL;
150        }
151        bits
152    }
153}
154
155#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
156/// Compression level and flags recorded together.
157pub struct CompressionValues {
158    /// Compression level metadata.
159    pub level: CompressionLevel,
160    /// Compression flag metadata.
161    pub flags: CompressionFlags,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165/// Media type recorded for an image.
166pub enum MediaType {
167    /// Removable physical media.
168    Removable,
169    /// Fixed physical media.
170    Fixed,
171    /// Optical media.
172    Optical,
173    /// Logical single-file collection.
174    SingleFiles,
175    /// Memory acquisition.
176    Memory,
177    /// An unrecognized on-disk media type value.
178    Unknown(u8),
179}
180
181#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
182/// Media flags recorded for an image.
183pub struct MediaFlags {
184    /// The image represents physical media.
185    pub physical: bool,
186    /// `FastBloc` acquisition flag.
187    pub fastbloc: bool,
188    /// Tableau acquisition flag.
189    pub tableau: bool,
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193/// EWF2 segment file version.
194pub struct SegmentFileVersion {
195    /// Major version number.
196    pub major: u8,
197    /// Minor version number.
198    pub minor: u8,
199}
200
201#[derive(Debug, Clone, Default, PartialEq, Eq)]
202/// Parsed media geometry and storage metadata.
203pub struct MediaInfo {
204    /// Number of sectors represented by each chunk, if present.
205    pub sectors_per_chunk: Option<u64>,
206    /// Bytes in each logical sector, if present.
207    pub bytes_per_sector: Option<u64>,
208    /// Total logical sector count, if present.
209    pub sector_count: Option<u64>,
210    /// Total logical chunk count, if present.
211    pub chunk_count: Option<u64>,
212    /// Error granularity in sectors, if present.
213    pub error_granularity: Option<u64>,
214    /// Segment set identifier, if present.
215    pub set_identifier: Option<[u8; 16]>,
216    /// EWF2 segment file version, if present.
217    pub ewf2_segment_file_version: Option<SegmentFileVersion>,
218    /// Compression method metadata, if present.
219    pub compression_method: Option<CompressionMethod>,
220    /// Compression level and flags metadata.
221    pub compression_values: CompressionValues,
222    /// Media type metadata, if present.
223    pub media_type: Option<MediaType>,
224    /// Media flags metadata.
225    pub media_flags: MediaFlags,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229/// Strictness used while opening an image.
230pub enum OpenStrictness {
231    /// Treat structural inconsistencies as errors.
232    Strict,
233    /// Accept selected recoverable inconsistencies.
234    Lenient,
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238/// Options that control how an image is opened and read.
239pub struct OpenOptions {
240    /// Structural validation policy.
241    pub strictness: OpenStrictness,
242    /// Number of decoded chunks retained in the read cache.
243    pub chunk_cache_size: usize,
244    /// Return zero-filled chunk data when a chunk checksum fails.
245    pub read_zero_chunk_on_error: bool,
246    /// Codepage used to decode EWF1 header values.
247    pub header_codepage: HeaderCodepage,
248    /// Date formatting applied when returning header date values.
249    pub header_values_date_format: HeaderDateFormat,
250    /// Maximum number of simultaneously open segment handles.
251    pub maximum_open_handles: Option<usize>,
252}
253
254impl Default for OpenOptions {
255    fn default() -> Self {
256        Self {
257            strictness: OpenStrictness::Strict,
258            chunk_cache_size: 64,
259            read_zero_chunk_on_error: false,
260            header_codepage: HeaderCodepage::Ascii,
261            header_values_date_format: HeaderDateFormat::Ctime,
262            maximum_open_handles: None,
263        }
264    }
265}
266
267#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
268/// Codepage used for EWF1 textual header values.
269pub enum HeaderCodepage {
270    /// ASCII header encoding.
271    #[default]
272    Ascii,
273    /// Windows-874 header encoding.
274    Windows874,
275    /// Windows-932 header encoding.
276    Windows932,
277    /// Windows-936 header encoding.
278    Windows936,
279    /// Windows-1250 header encoding.
280    Windows1250,
281    /// Windows-1251 header encoding.
282    Windows1251,
283    /// Windows-1252 header encoding.
284    Windows1252,
285    /// Windows-1253 header encoding.
286    Windows1253,
287    /// Windows-1254 header encoding.
288    Windows1254,
289    /// Windows-1255 header encoding.
290    Windows1255,
291    /// Windows-1256 header encoding.
292    Windows1256,
293    /// Windows-1257 header encoding.
294    Windows1257,
295    /// Windows-1258 header encoding.
296    Windows1258,
297}
298
299impl HeaderCodepage {
300    /// Returns the EWF numeric codepage identifier.
301    pub fn as_i32(self) -> i32 {
302        match self {
303            Self::Ascii => 20_127,
304            Self::Windows874 => 874,
305            Self::Windows932 => 932,
306            Self::Windows936 => 936,
307            Self::Windows1250 => 1250,
308            Self::Windows1251 => 1251,
309            Self::Windows1252 => 1252,
310            Self::Windows1253 => 1253,
311            Self::Windows1254 => 1254,
312            Self::Windows1255 => 1255,
313            Self::Windows1256 => 1256,
314            Self::Windows1257 => 1257,
315            Self::Windows1258 => 1258,
316        }
317    }
318
319    /// Converts an EWF numeric codepage identifier.
320    pub fn from_i32(value: i32) -> Option<Self> {
321        Some(match value {
322            20_127 => Self::Ascii,
323            874 => Self::Windows874,
324            932 => Self::Windows932,
325            936 => Self::Windows936,
326            1250 => Self::Windows1250,
327            1251 => Self::Windows1251,
328            1252 => Self::Windows1252,
329            1253 => Self::Windows1253,
330            1254 => Self::Windows1254,
331            1255 => Self::Windows1255,
332            1256 => Self::Windows1256,
333            1257 => Self::Windows1257,
334            1258 => Self::Windows1258,
335            _ => return None,
336        })
337    }
338}
339
340#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
341/// Formatting applied when returning parsed EWF header date values.
342pub enum HeaderDateFormat {
343    /// Day-month date order.
344    DayMonth,
345    /// Month-day date order.
346    MonthDay,
347    /// ISO 8601-style date formatting.
348    Iso8601,
349    /// C `ctime`-style date formatting.
350    #[default]
351    Ctime,
352}
353
354impl HeaderDateFormat {
355    /// Returns the numeric date-format identifier used by this crate.
356    pub fn as_i32(self) -> i32 {
357        match self {
358            Self::DayMonth => 1,
359            Self::MonthDay => 2,
360            Self::Iso8601 => 3,
361            Self::Ctime => 4,
362        }
363    }
364
365    /// Converts a numeric date-format identifier used by this crate.
366    pub fn from_i32(value: i32) -> Option<Self> {
367        Some(match value {
368            1 => Self::DayMonth,
369            2 => Self::MonthDay,
370            3 => Self::Iso8601,
371            4 => Self::Ctime,
372            _ => return None,
373        })
374    }
375}
376
377#[derive(Debug, Clone, Default, PartialEq, Eq)]
378/// Parsed case and acquisition metadata.
379pub struct EwfMetadata {
380    /// Case number header value.
381    pub case_number: Option<String>,
382    /// Evidence number header value.
383    pub evidence_number: Option<String>,
384    /// Examiner name header value.
385    pub examiner: Option<String>,
386    /// Case description header value.
387    pub description: Option<String>,
388    /// Notes header value.
389    pub notes: Option<String>,
390    /// Acquisition software name.
391    pub acquisition_software: Option<String>,
392    /// Acquisition software version.
393    pub acquisition_software_version: Option<String>,
394    /// Acquisition operating system version.
395    pub os_version: Option<String>,
396    /// Acquisition date header value.
397    pub acquisition_date: Option<String>,
398    /// System date header value.
399    pub system_date: Option<String>,
400    /// Password header value, when present in metadata.
401    pub password: Option<String>,
402    /// Non-standard or otherwise unmapped header values.
403    pub header_values: BTreeMap<String, String>,
404}
405
406impl EwfMetadata {
407    /// Replaces this metadata with another metadata value.
408    pub fn copy_header_values_from(&mut self, source: &EwfMetadata) {
409        *self = source.clone();
410    }
411
412    /// Returns a header value by its EWF identifier.
413    pub fn header_value(&self, identifier: &str) -> Option<&str> {
414        self.standard_header_value(identifier)
415            .or_else(|| self.header_values.get(identifier).map(String::as_str))
416    }
417
418    /// Returns a header value, applying date formatting for known date fields.
419    pub fn header_value_with_date_format(
420        &self,
421        identifier: &str,
422        date_format: HeaderDateFormat,
423    ) -> Option<Cow<'_, str>> {
424        let value = self.header_value(identifier)?;
425        if matches!(identifier, "acquiry_date" | "system_date") {
426            Some(format_header_date_value(value, date_format))
427        } else {
428            Some(Cow::Borrowed(value))
429        }
430    }
431
432    /// Sets a header value by its EWF identifier and returns the previous value.
433    pub fn set_header_value(
434        &mut self,
435        identifier: &str,
436        value: impl Into<String>,
437    ) -> Option<String> {
438        let previous = self.header_value(identifier).map(str::to_owned);
439        let value = value.into();
440
441        self.header_values.remove(identifier);
442        match identifier {
443            "case_number" => self.case_number = Some(value),
444            "description" => self.description = Some(value),
445            "examiner_name" => self.examiner = Some(value),
446            "evidence_number" => self.evidence_number = Some(value),
447            "notes" => self.notes = Some(value),
448            "acquiry_date" => self.acquisition_date = Some(value),
449            "system_date" => self.system_date = Some(value),
450            "acquiry_operating_system" => self.os_version = Some(value),
451            "acquiry_software" => self.acquisition_software = Some(value),
452            "acquiry_software_version" => self.acquisition_software_version = Some(value),
453            "password" => self.password = Some(value),
454            _ => {
455                self.header_values.insert(identifier.to_string(), value);
456            }
457        }
458
459        previous
460    }
461
462    /// Returns the number of available header values.
463    pub fn number_of_header_values(&self) -> usize {
464        let standard_count = STANDARD_HEADER_VALUE_IDENTIFIERS
465            .iter()
466            .filter(|identifier| self.header_value(identifier).is_some())
467            .count();
468        let generic_count = self
469            .header_values
470            .keys()
471            .filter(|identifier| !is_standard_header_value_identifier(identifier))
472            .count();
473        standard_count + generic_count
474    }
475
476    /// Returns the header value identifier at a stable enumeration index.
477    pub fn header_value_identifier(&self, index: usize) -> Option<&str> {
478        let mut remaining = index;
479        for identifier in STANDARD_HEADER_VALUE_IDENTIFIERS {
480            if self.header_value(identifier).is_some() {
481                if remaining == 0 {
482                    return Some(identifier);
483                }
484                remaining -= 1;
485            }
486        }
487
488        for identifier in self.header_values.keys() {
489            if is_standard_header_value_identifier(identifier) {
490                continue;
491            }
492            if remaining == 0 {
493                return Some(identifier);
494            }
495            remaining -= 1;
496        }
497
498        None
499    }
500
501    fn standard_header_value(&self, identifier: &str) -> Option<&str> {
502        match identifier {
503            "case_number" => self.case_number.as_deref(),
504            "description" => self.description.as_deref(),
505            "examiner_name" => self.examiner.as_deref(),
506            "evidence_number" => self.evidence_number.as_deref(),
507            "notes" => self.notes.as_deref(),
508            "acquiry_date" => self.acquisition_date.as_deref(),
509            "system_date" => self.system_date.as_deref(),
510            "acquiry_operating_system" => self.os_version.as_deref(),
511            "acquiry_software" => self.acquisition_software.as_deref(),
512            "acquiry_software_version" => self.acquisition_software_version.as_deref(),
513            "password" => self.password.as_deref(),
514            _ => None,
515        }
516    }
517}
518
519const STANDARD_HEADER_VALUE_IDENTIFIERS: &[&str] = &[
520    "case_number",
521    "description",
522    "examiner_name",
523    "evidence_number",
524    "notes",
525    "acquiry_date",
526    "system_date",
527    "acquiry_operating_system",
528    "acquiry_software",
529    "acquiry_software_version",
530    "password",
531    "compression_level",
532    "model",
533    "serial_number",
534];
535
536fn is_standard_header_value_identifier(identifier: &str) -> bool {
537    STANDARD_HEADER_VALUE_IDENTIFIERS.contains(&identifier)
538}
539
540#[derive(Debug, Clone, Default, PartialEq, Eq)]
541/// Stored hash values parsed from an image.
542pub struct StoredHashes {
543    /// Parsed MD5 hash bytes, if a valid MD5 value was stored.
544    pub md5: Option<[u8; 16]>,
545    /// Parsed SHA1 hash bytes, if a valid SHA1 value was stored.
546    pub sha1: Option<[u8; 20]>,
547    /// Stored hash strings keyed by hash identifier.
548    pub hash_values: BTreeMap<String, String>,
549}
550
551impl StoredHashes {
552    /// Returns a stored hash value by identifier.
553    pub fn hash_value(&self, identifier: &str) -> Option<&str> {
554        self.hash_values.get(identifier).map(String::as_str)
555    }
556
557    /// Sets a stored hash value and returns the previous string value.
558    pub fn set_hash_value(
559        &mut self,
560        identifier: impl Into<String>,
561        value: impl Into<String>,
562    ) -> Option<String> {
563        let identifier = identifier.into();
564        let value = value.into();
565        set_typed_hash_value(&identifier, &value, &mut self.md5, &mut self.sha1);
566        self.hash_values.insert(identifier, value)
567    }
568
569    /// Returns the number of stored hash values.
570    pub fn number_of_hash_values(&self) -> usize {
571        self.hash_values.len()
572    }
573
574    /// Returns the hash identifier at a stable enumeration index.
575    pub fn hash_value_identifier(&self, index: usize) -> Option<&str> {
576        self.hash_values.keys().nth(index).map(String::as_str)
577    }
578}
579
580pub(crate) fn set_typed_hash_value(
581    identifier: &str,
582    value: &str,
583    md5: &mut Option<[u8; 16]>,
584    sha1: &mut Option<[u8; 20]>,
585) {
586    if identifier.eq_ignore_ascii_case("MD5") {
587        if let Some(parsed) = parse_hex_array(value) {
588            *md5 = Some(parsed);
589        }
590    } else if identifier.eq_ignore_ascii_case("SHA1")
591        && let Some(parsed) = parse_hex_array(value)
592    {
593        *sha1 = Some(parsed);
594    }
595}
596
597fn parse_hex_array<const N: usize>(text: &str) -> Option<[u8; N]> {
598    if text.len() != N * 2 {
599        return None;
600    }
601
602    let mut bytes = [0; N];
603    for (index, pair) in text.as_bytes().chunks_exact(2).enumerate() {
604        let high = hex_nibble(pair[0])?;
605        let low = hex_nibble(pair[1])?;
606        bytes[index] = (high << 4) | low;
607    }
608    Some(bytes)
609}
610
611fn hex_nibble(value: u8) -> Option<u8> {
612    match value {
613        b'0'..=b'9' => Some(value - b'0'),
614        b'a'..=b'f' => Some(value - b'a' + 10),
615        b'A'..=b'F' => Some(value - b'A' + 10),
616        _ => None,
617    }
618}
619
620#[derive(Debug, Clone, PartialEq, Eq)]
621/// Acquisition error range recorded in image metadata.
622pub struct AcquisitionError {
623    /// First sector affected by the acquisition error.
624    pub first_sector: u64,
625    /// Number of sectors affected by the acquisition error.
626    pub sector_count: u64,
627}
628
629#[derive(Debug, Clone, PartialEq, Eq)]
630/// Inclusive start plus count sector range.
631pub struct SectorRange {
632    /// First sector in the range.
633    pub first_sector: u64,
634    /// Number of sectors in the range.
635    pub sector_count: u64,
636}
637
638#[derive(Debug, Clone, PartialEq, Eq)]
639/// Memory acquisition extent recorded in pages.
640pub struct MemoryExtent {
641    /// First memory page in the extent.
642    pub start_page: u64,
643    /// Number of pages in the extent.
644    pub page_count: u64,
645}
646
647#[derive(Debug, Clone, Copy, PartialEq, Eq)]
648/// Encoding used for a data chunk payload.
649pub enum DataChunkEncoding {
650    /// Uncompressed chunk data.
651    Raw,
652    /// zlib-compressed chunk data.
653    Zlib,
654    /// BZip2-compressed chunk data.
655    Bzip2,
656    /// Pattern-fill chunk data with the repeated pattern value.
657    PatternFill(u64),
658}
659
660#[derive(Debug, Clone, PartialEq, Eq)]
661/// Decoded logical data chunk.
662pub struct DataChunk {
663    /// Zero-based logical chunk index.
664    pub chunk_index: u64,
665    /// Logical byte offset of the chunk.
666    pub logical_offset: u64,
667    /// Logical byte size of the decoded chunk.
668    pub logical_size: usize,
669    /// Encoded byte size as stored in the segment.
670    pub encoded_size: u64,
671    /// Encoding used by the stored chunk.
672    pub encoding: DataChunkEncoding,
673    /// Whether the chunk was returned under a corruption-tolerant read policy.
674    pub corrupted: bool,
675    /// Decoded chunk bytes.
676    pub data: Vec<u8>,
677}
678
679impl DataChunk {
680    /// Returns whether this chunk was marked corrupted while reading.
681    pub fn is_corrupted(&self) -> bool {
682        self.corrupted
683    }
684
685    /// Copies decoded chunk bytes into `buffer`.
686    pub fn read_buffer(&self, buffer: &mut [u8]) -> Result<usize> {
687        Ok(copy_to_buffer(&self.data, buffer))
688    }
689
690    /// Replaces this chunk with raw bytes from `buffer`.
691    ///
692    /// # Errors
693    ///
694    /// Returns an error if the buffer length does not fit in EWF chunk metadata.
695    pub fn write_buffer(&mut self, buffer: &[u8]) -> Result<usize> {
696        self.data.clear();
697        self.data.extend_from_slice(buffer);
698        self.logical_size = buffer.len();
699        self.encoded_size = u64::try_from(buffer.len())
700            .map_err(|_| EwfError::Malformed("data chunk buffer size overflow".into()))?;
701        self.encoding = DataChunkEncoding::Raw;
702        self.corrupted = false;
703        Ok(buffer.len())
704    }
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708/// Encoded data chunk as stored in an EWF segment.
709pub struct EncodedDataChunk {
710    /// Zero-based logical chunk index.
711    pub chunk_index: u64,
712    /// Logical byte offset of the chunk.
713    pub logical_offset: u64,
714    /// Logical byte size after decoding.
715    pub logical_size: usize,
716    /// Encoded byte size stored in the segment.
717    pub encoded_size: u64,
718    /// Encoding used by the chunk payload.
719    pub encoding: DataChunkEncoding,
720    /// Whether raw chunk data includes a checksum trailer.
721    pub has_checksum: bool,
722    /// Encoded chunk bytes.
723    pub data: Vec<u8>,
724}
725
726impl EncodedDataChunk {
727    /// Decodes this chunk and copies logical bytes into `buffer`.
728    ///
729    /// # Errors
730    ///
731    /// Returns an error if checksum validation or decompression fails.
732    pub fn read_buffer(&self, buffer: &mut [u8]) -> Result<usize> {
733        if self.has_checksum && self.encoding == DataChunkEncoding::Raw {
734            validate_raw_data_chunk_checksum(&self.data, self.logical_size)?;
735        }
736
737        let decoded = decode_chunk(
738            &self.data,
739            data_chunk_encoding(self.encoding),
740            self.logical_size,
741        )?;
742        Ok(copy_to_buffer(&decoded, buffer))
743    }
744}
745
746fn copy_to_buffer(data: &[u8], buffer: &mut [u8]) -> usize {
747    let read_size = data.len().min(buffer.len());
748    buffer[..read_size].copy_from_slice(&data[..read_size]);
749    read_size
750}
751
752fn data_chunk_encoding(encoding: DataChunkEncoding) -> ChunkEncoding {
753    match encoding {
754        DataChunkEncoding::Raw => ChunkEncoding::Raw,
755        DataChunkEncoding::Zlib => ChunkEncoding::Zlib,
756        DataChunkEncoding::Bzip2 => ChunkEncoding::Bzip2,
757        DataChunkEncoding::PatternFill(pattern) => ChunkEncoding::PatternFill(pattern),
758    }
759}
760
761fn validate_raw_data_chunk_checksum(encoded: &[u8], logical_size: usize) -> Result<()> {
762    let checksum_end = logical_size
763        .checked_add(4)
764        .ok_or_else(|| EwfError::Malformed("raw chunk checksum offset overflow".into()))?;
765    if encoded.len() < checksum_end {
766        return Err(EwfError::Malformed(
767            "raw chunk checksum trailer is missing".into(),
768        ));
769    }
770
771    let stored = u32::from_le_bytes(
772        encoded[logical_size..checksum_end]
773            .try_into()
774            .expect("raw chunk checksum slice has fixed size"),
775    );
776    let calculated = adler32(&encoded[..logical_size]);
777    if stored != calculated {
778        return Err(EwfError::Malformed("raw chunk checksum mismatch".into()));
779    }
780
781    Ok(())
782}
783
784fn adler32(data: &[u8]) -> u32 {
785    const MOD_ADLER: u32 = 65_521;
786    let mut a = 1_u32;
787    let mut b = 0_u32;
788    for byte in data {
789        a = (a + u32::from(*byte)) % MOD_ADLER;
790        b = (b + a) % MOD_ADLER;
791    }
792    (b << 16) | a
793}
794
795#[derive(Debug, Clone, Copy, PartialEq, Eq)]
796/// Type of a logical single-file catalog entry.
797pub enum SingleFileEntryType {
798    /// Regular file entry.
799    File,
800    /// Directory entry.
801    Directory,
802    /// Entry type not recognized by this crate.
803    Unknown,
804}
805
806#[derive(Debug, Clone, Copy, PartialEq, Eq)]
807/// Data extent for a logical single-file entry.
808pub struct SingleFileExtent {
809    /// Logical media offset where this extent's data starts.
810    pub data_offset: u64,
811    /// Number of bytes represented by this extent.
812    pub data_size: u64,
813    /// Whether the extent is sparse and should read as zeroes.
814    pub sparse: bool,
815}
816
817#[derive(Debug, Clone, Default, PartialEq, Eq)]
818/// Source record for logical single-file metadata.
819pub struct SingleFileSource {
820    /// Source identifier.
821    pub identifier: Option<i32>,
822    /// Source name.
823    pub name: Option<String>,
824    /// Evidence number associated with the source.
825    pub evidence_number: Option<String>,
826    /// Source location.
827    pub location: Option<String>,
828    /// Source device GUID.
829    pub device_guid: Option<String>,
830    /// Primary source device GUID.
831    pub primary_device_guid: Option<String>,
832    /// Source drive type code.
833    pub drive_type: Option<char>,
834    /// Source manufacturer.
835    pub manufacturer: Option<String>,
836    /// Source model.
837    pub model: Option<String>,
838    /// Source serial number.
839    pub serial_number: Option<String>,
840    /// Source domain.
841    pub domain: Option<String>,
842    /// Source IP address.
843    pub ip_address: Option<String>,
844    /// Source MAC address.
845    pub mac_address: Option<String>,
846    /// Source size in bytes.
847    pub size: Option<u64>,
848    /// Source logical offset.
849    pub logical_offset: Option<i64>,
850    /// Source physical offset.
851    pub physical_offset: Option<i64>,
852    /// Source acquisition timestamp.
853    pub acquisition_time: Option<i64>,
854    /// Source MD5 hash string.
855    pub md5: Option<String>,
856    /// Source SHA1 hash string.
857    pub sha1: Option<String>,
858}
859
860#[derive(Debug, Clone, Default, PartialEq, Eq)]
861/// Subject record for logical single-file metadata.
862pub struct SingleFileSubject {
863    /// Subject identifier.
864    pub identifier: Option<u32>,
865    /// Subject name.
866    pub name: Option<String>,
867}
868
869#[derive(Debug, Clone, Default, PartialEq, Eq)]
870/// Access-control entry for a logical single-file entry.
871pub struct SingleFilePermission {
872    /// Permission name.
873    pub name: Option<String>,
874    /// Security identifier or permission identifier.
875    pub identifier: Option<String>,
876    /// Permission property type.
877    pub property_type: Option<u32>,
878    /// Access mask value.
879    pub access_mask: Option<u32>,
880    /// ACE flags value.
881    pub ace_flags: Option<u32>,
882}
883
884#[derive(Debug, Clone, Default, PartialEq, Eq)]
885/// Access-control group for logical single-file entries.
886pub struct SingleFilePermissionGroup {
887    /// Group name.
888    pub name: Option<String>,
889    /// Security identifier or group identifier.
890    pub identifier: Option<String>,
891    /// Permission group property type.
892    pub property_type: Option<u32>,
893    /// Group access mask value.
894    pub access_mask: Option<u32>,
895    /// Group ACE flags value.
896    pub ace_flags: Option<u32>,
897    /// Permissions in the group.
898    pub permissions: Vec<SingleFilePermission>,
899}
900
901#[derive(Debug, Clone, Default, PartialEq, Eq)]
902/// Extended attribute for a logical single-file entry.
903pub struct SingleFileAttribute {
904    /// Attribute name.
905    pub name: Option<String>,
906    /// Attribute value.
907    pub value: Option<String>,
908}
909
910#[derive(Debug, Clone, Default, PartialEq, Eq)]
911/// Entry in a logical single-file catalog.
912pub struct SingleFileEntry {
913    /// Entry identifier.
914    pub identifier: Option<u64>,
915    /// Entry type.
916    pub file_entry_type: Option<SingleFileEntryType>,
917    /// Raw entry flags.
918    pub flags: Option<u32>,
919    /// Entry GUID.
920    pub guid: Option<String>,
921    /// Entry name.
922    pub name: Option<String>,
923    /// Short entry name.
924    pub short_name: Option<String>,
925    /// Logical file size in bytes.
926    pub size: Option<u64>,
927    /// Logical media offset for the entry data.
928    pub logical_offset: Option<i64>,
929    /// Physical source offset for the entry data.
930    pub physical_offset: Option<i64>,
931    /// Duplicate-data logical media offset.
932    pub duplicate_data_offset: Option<i64>,
933    /// Identifier of the associated source record.
934    pub source_identifier: Option<i32>,
935    /// Identifier of the associated subject record.
936    pub subject_identifier: Option<u32>,
937    /// Index of the associated permission group.
938    pub permission_group_index: Option<i32>,
939    /// Raw record type.
940    pub record_type: Option<u32>,
941    /// Creation timestamp.
942    pub creation_time: Option<i64>,
943    /// Modification timestamp.
944    pub modification_time: Option<i64>,
945    /// Access timestamp.
946    pub access_time: Option<i64>,
947    /// Entry metadata modification timestamp.
948    pub entry_modification_time: Option<i64>,
949    /// Deletion timestamp.
950    pub deletion_time: Option<i64>,
951    /// Entry MD5 hash string.
952    pub md5: Option<String>,
953    /// Entry SHA1 hash string.
954    pub sha1: Option<String>,
955    /// Data extents for the entry.
956    pub extents: Vec<SingleFileExtent>,
957    /// Extended attributes for the entry.
958    pub attributes: Vec<SingleFileAttribute>,
959    /// Child entries.
960    pub children: Vec<SingleFileEntry>,
961}
962
963#[derive(Debug, Clone, Default, PartialEq, Eq)]
964/// Logical single-file catalog metadata.
965pub struct SingleFilesInfo {
966    /// Byte size of the single-file data section.
967    pub data_size: u64,
968    /// Root catalog entry.
969    pub root: SingleFileEntry,
970    /// Source records.
971    pub sources: Vec<SingleFileSource>,
972    /// Subject records.
973    pub subjects: Vec<SingleFileSubject>,
974    /// Permission groups.
975    pub permission_groups: Vec<SingleFilePermissionGroup>,
976}
977
978#[derive(Debug, Clone, Default, PartialEq, Eq)]
979/// Preserved auxiliary EWF2 single-file tables.
980pub struct SingleFilesAuxTables {
981    /// Raw entries from EWF2 single-files table `0x21`.
982    pub table_0x21_entries: Vec<u64>,
983    /// MD5 hash table entries.
984    pub md5_hashes: Vec<[u8; 16]>,
985    /// Raw entries from EWF2 single-files table `0x23`.
986    pub table_0x23_entries: Vec<u64>,
987}
988
989impl SingleFilesAuxTables {
990    /// Returns whether no auxiliary single-file table data is present.
991    pub fn is_empty(&self) -> bool {
992        self.table_0x21_entries.is_empty()
993            && self.md5_hashes.is_empty()
994            && self.table_0x23_entries.is_empty()
995    }
996}
997
998#[derive(Debug, Clone, PartialEq, Eq)]
999/// Parsed summary of an opened EWF image.
1000pub struct ImageInfo {
1001    /// Top-level EWF format generation.
1002    pub format: Format,
1003    /// Inferred producer/profile.
1004    pub format_profile: FormatProfile,
1005    /// Number of opened segment files.
1006    pub segment_count: usize,
1007    /// Segment paths or supplied-reader labels.
1008    pub segment_paths: Vec<PathBuf>,
1009    /// Logical chunk size in bytes.
1010    pub chunk_size: u64,
1011    /// Logical media size in bytes.
1012    pub logical_size: u64,
1013    /// Whether the image has a terminal complete marker.
1014    pub acquisition_complete: bool,
1015    /// Header codepage used for decoded EWF1 values.
1016    pub header_codepage: HeaderCodepage,
1017    /// Date format used when returning header date values.
1018    pub header_values_date_format: HeaderDateFormat,
1019    /// Media geometry and flags.
1020    pub media: MediaInfo,
1021    /// Parsed case and acquisition metadata.
1022    pub metadata: EwfMetadata,
1023    /// Stored hash values.
1024    pub stored_hashes: StoredHashes,
1025    /// Acquisition error ranges.
1026    pub acquisition_errors: Vec<AcquisitionError>,
1027    /// Memory acquisition extents.
1028    pub memory_extents: Vec<MemoryExtent>,
1029    /// Logical single-file catalog, if present.
1030    pub single_files: Option<SingleFilesInfo>,
1031    /// Preserved EWF2 single-file auxiliary table data.
1032    pub ewf2_single_files_tables: SingleFilesAuxTables,
1033    /// Raw EWF2 increment data sections.
1034    pub ewf2_increment_data: Vec<Vec<u8>>,
1035    /// Raw EWF2 final information section.
1036    pub ewf2_final_information: Option<Vec<u8>>,
1037    /// EWF2 restart data text.
1038    pub ewf2_restart_data: Option<String>,
1039    /// EWF2 analytical data text.
1040    pub ewf2_analytical_data: Option<String>,
1041    /// Session sector ranges.
1042    pub sessions: Vec<SectorRange>,
1043    /// Track sector ranges.
1044    pub tracks: Vec<SectorRange>,
1045}
1046
1047#[cfg(feature = "verify")]
1048#[derive(Debug, Clone, PartialEq, Eq)]
1049/// Result of streamed logical media hash verification.
1050pub struct VerifyResult {
1051    /// MD5 hash computed from the logical media stream.
1052    pub computed_md5: Option<[u8; 16]>,
1053    /// SHA1 hash computed from the logical media stream.
1054    pub computed_sha1: Option<[u8; 20]>,
1055    /// Whether computed MD5 matched the stored MD5, or `None` if no MD5 was stored.
1056    pub md5_match: Option<bool>,
1057    /// Whether computed SHA1 matched the stored SHA1, or `None` if no SHA1 was stored.
1058    pub sha1_match: Option<bool>,
1059}