Skip to main content

ewf_image/
lib.rs

1//! Rust library for reading and writing Expert Witness Format forensic images.
2//!
3//! `ewf_image` provides direct Rust APIs for working with Expert Witness Format
4//! images. It can open physical, logical, SMART, and EWF2 segment families,
5//! expose metadata and stored hashes, read the logical media stream, walk
6//! logical single-file catalogs, and create EWF output. An optional acquisition
7//! CLI is available; a filesystem mount layer is not currently implemented.
8//!
9//! # Terminology
10//!
11//! Logical media size is the decoded byte length exposed by [`Image::media_size`]
12//! and [`ImageInfo::logical_size`]. Segment set size is the total byte length of
13//! the opened EWF container files, as reported by [`Image::segment_set_size`].
14//! Chunks are the stored allocation units used by EWF tables. Logical EWF
15//! images can also contain a single-file catalog, where each entry describes a
16//! file-like object stored inside the image.
17//!
18//! # Supported container families
19//!
20//! - EWF1 physical `.E01` / EVF images.
21//! - X-Ways AES-128/AES-256 encrypted EWF1 images.
22//! - EWF1 logical `.L01` / LVF images.
23//! - EWF1 SMART `.S01` images.
24//! - EWF2 physical `.Ex01` images.
25//! - EWF2 logical `.Lx01` images.
26//!
27//! # Reading
28//!
29//! ```no_run
30//! use std::io::Read;
31//!
32//! fn main() -> ewf_image::Result<()> {
33//!     let image = ewf_image::Image::open("case.E01")?;
34//!     let info = image.info();
35//!
36//!     println!("{:?}: {} bytes", info.format, info.logical_size);
37//!     println!("segments: {}", image.number_of_segments());
38//!
39//!     let mut sector = vec![0; 512];
40//!     image.cursor().read_exact(&mut sector)?;
41//!
42//!     let mut later_sector = vec![0; 512];
43//!     image.read_at(&mut later_sector, 4096)?;
44//!
45//!     Ok(())
46//! }
47//! ```
48//!
49//! Password-protected X-Ways EWF1 images use the same read APIs after a
50//! password-aware open:
51//!
52//! ```no_run
53//! fn main() -> ewf_image::Result<()> {
54//!     let password = ewf_image::EwfPassword::utf8("operator-supplied-password");
55//!     let image = ewf_image::Image::open_with_password("case.E01", &password)?;
56//!     println!("{:?}", image.encryption_info());
57//!     Ok(())
58//! }
59//! ```
60//!
61//! # Metadata and hashes
62//!
63//! ```no_run
64//! fn main() -> ewf_image::Result<()> {
65//!     let image = ewf_image::Image::open("case.E01")?;
66//!
67//!     if let Some(case_number) = image.header_value("case_number") {
68//!         println!("case: {case_number}");
69//!     }
70//!
71//!     if let Some(md5) = image.hash_value("MD5") {
72//!         println!("stored MD5: {md5}");
73//!     }
74//!
75//!     #[cfg(feature = "verify")]
76//!     {
77//!         let verification = image.verify()?;
78//!         println!("MD5 match: {:?}", verification.md5_match);
79//!         println!("SHA1 match: {:?}", verification.sha1_match);
80//!     }
81//!
82//!     Ok(())
83//! }
84//! ```
85//!
86//! # Writing
87//!
88//! ```no_run
89//! use std::fs::File;
90//!
91//! fn main() -> ewf_image::Result<()> {
92//!     let mut input = File::open("disk.raw")?;
93//!
94//!     let mut options = ewf_image::WriteOptions::default();
95//!     options.format = ewf_image::WriteFormat::Ewf2Physical;
96//!     options.compression = ewf_image::WriteCompression::Zlib;
97//!     options.metadata.set_header_value("case_number", "CASE-001");
98//!
99//!     let mut writer = ewf_image::EwfWriter::create("case.Ex01", options)?;
100//!     std::io::copy(&mut input, &mut writer)?;
101//!     writer.finish()?;
102//!
103//!     Ok(())
104//! }
105//! ```
106//!
107//! # Streaming acquisition
108//!
109//! [`AcquisitionWriter`] seals physical E01 segments as input arrives and can
110//! resume sealed checkpoints without rewriting them. It requires a known,
111//! sector-aligned source size and a filesystem supporting hard links.
112//!
113//! ```no_run
114//! use std::fs::File;
115//! use std::ops::ControlFlow;
116//! use ewf_image::{AcquisitionOptions, AcquisitionReadOptions, AcquisitionStatus, AcquisitionWriter};
117//!
118//! fn continue_acquisition(source_identity: [u8; 32]) -> ewf_image::Result<()> {
119//!     let mut input = File::open("disk.raw")?;
120//!     let options = AcquisitionOptions::new(input.metadata()?.len());
121//!     // Use create instead of resume for the first acquisition session.
122//!     // The caller must bind source_identity to a stable source snapshot.
123//!     let mut writer = AcquisitionWriter::resume("case.E01", &options, source_identity)?;
124//!     let outcome = writer.acquire_with_progress(
125//!         &mut input, &AcquisitionReadOptions::default(), |progress| {
126//!             println!("{} accepted; {} checkpointed", progress.bytes_written, progress.checkpoint_bytes);
127//!             // Return Break(()) when the application's cancellation flag is set.
128//!             ControlFlow::Continue(())
129//!         }
130//!     )?;
131//!     if outcome.status == AcquisitionStatus::Complete {
132//!         writer.finish()?;
133//!     }
134//!     Ok(())
135//! }
136//! ```
137//!
138//! # Feature flags
139//!
140//! - `verify` is enabled by default and adds `Image::verify()` plus
141//!   `VerifyResult` for streamed MD5/SHA1 verification. Stored hash parsing,
142//!   EWF2 section integrity checks, and writer hash support are available
143//!   without this feature.
144//! - `external-fixtures` enables ignored integration tests that require local
145//!   EWF corpora and external EWF tools. It does not change library behavior.
146//! - `fuzzing` exposes the logical catalog parser to the isolated fuzz workspace.
147//! - `parallel` enables multiple decompression workers for verification and
148//!   analysis. It implies `verify`; scans remain single-threaded by default.
149//! - `serde` enables serialization of analysis, verification, recovery, and
150//!   section-summary results.
151//!
152//! # Analysis and recovery
153//!
154//! [`Image::sections`] exposes descriptor locations. [`Image::open_sources`]
155//! accepts [`SegmentSource`] backings with independent positioned reads.
156//! With `verify`, `verify_with_options` computes SHA256 and compares external
157//! references; `analyze` returns typed findings and explicit media coverage.
158//! [`EwfRecovery`] separately recovers physical raw/zlib EWF1 data with
159//! provenance and exclusive creation of output files.
160//!
161//! # Limitations
162//!
163//! X-Ways encrypted EWF1 reading is supported. Encrypted EWF2 images are
164//! detected and rejected, and encrypted writing is not implemented.
165//! Secondary/shadow target mirroring is supported by the file-backed writer.
166//! Base-plus-overlay delta/shadow images are not implemented.
167
168#![forbid(unsafe_code)]
169
170mod codepage;
171mod date_time;
172mod decode;
173mod encryption;
174mod error;
175mod format;
176mod hashes;
177mod image;
178mod index;
179#[cfg(feature = "verify")]
180mod integrity;
181#[cfg(feature = "verify")]
182mod logical_verify;
183mod metadata;
184mod publication;
185mod reader_cache;
186mod reader_statistics;
187mod sections;
188mod segment;
189mod signature;
190mod single_files;
191mod source;
192mod types;
193#[cfg(feature = "verify")]
194mod verify;
195mod writer;
196
197pub use encryption::{EncryptionInfo, EncryptionMethod, EwfPassword};
198pub use error::{EwfError, Result};
199/// Direct catalog parsing for the isolated fuzz workspace.
200#[cfg(feature = "fuzzing")]
201#[doc(hidden)]
202pub fn parse_single_files_for_fuzzing(data: &[u8], ewf1: bool) -> Result<SingleFilesInfo> {
203    if ewf1 {
204        single_files::parse_ewf1_single_files_data(data)
205    } else {
206        single_files::parse_ewf2_single_files_data(data)
207    }
208}
209pub use image::recovery::{
210    EwfRecovery, RecoveryNotice, RecoveryOptions, RecoveryProgress, RecoveryRange, RecoveryReport,
211    RecoveryStatus,
212};
213pub use image::{Image, ImageCursor, SegmentReader, SingleFileCursor};
214#[cfg(feature = "verify")]
215pub use logical_verify::{SingleFileProgress, SingleFileVerification};
216pub use reader_statistics::{ReaderCacheInfo, ReaderStatistics};
217pub use sections::{SectionInfo, SectionKind};
218pub use signature::{
219    check_file_corruption, check_file_encryption, check_file_signature,
220    check_segment_files_corruption, check_segment_files_encryption,
221};
222pub use single_files::SINGLE_FILE_PATH_SEPARATOR;
223pub use source::{SegmentReadAt, SegmentSource};
224pub use types::{
225    AcquisitionError, ChunkCacheCapacity, CompressionFlags, CompressionLevel, CompressionMethod,
226    CompressionValues, DataChunk, DataChunkEncoding, EncodedDataChunk, EwfMetadata, Format,
227    FormatProfile, HeaderCodepage, HeaderDateFormat, ImageInfo, MediaFlags, MediaInfo, MediaType,
228    MemoryExtent, OpenOptions, OpenStrictness, SectorRange, SegmentFileVersion,
229    SingleFileAttribute, SingleFileEntry, SingleFileEntryType, SingleFileExtent,
230    SingleFilePermission, SingleFilePermissionGroup, SingleFileSource, SingleFileSubject,
231    SingleFilesAuxTables, SingleFilesInfo, StoredHashes,
232};
233pub use writer::{
234    AcquisitionCheckpoint, AcquisitionOperationPhase, AcquisitionOperationProgress,
235    AcquisitionOptions, AcquisitionOutcome, AcquisitionProgress, AcquisitionReadOptions,
236    AcquisitionStatus, AcquisitionWriter, EwfWriter, LogicalEntryMetadata, LogicalWriteProgress,
237    LogicalWriter, SequentialOptions, SequentialWriter, UnreadableSectorPolicy, WriteCompression,
238    WriteCompressionLevel, WriteCompressionValues, WriteFormat, WriteHashes, WriteMediaProfile,
239    WriteOptions, WriteResult,
240};
241
242#[cfg(feature = "verify")]
243pub use integrity::{
244    IntegrityFinding, IntegrityFindingKind, IntegrityReport, IntegritySeverity, MediaScanStatus,
245    analyze_path, analyze_path_with_password, analyze_path_with_progress,
246    analyze_path_with_progress_and_password,
247};
248#[cfg(feature = "verify")]
249pub use types::VerifyResult;
250#[cfg(feature = "verify")]
251pub use verify::{
252    ComputedHashes, HashAlgorithm, HashComparison, HashReference, VerificationReport,
253    VerifyOptions, VerifyProgress,
254};