Skip to main content

ewf_image/
writer.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::fs::{self, File};
4use std::io::{Read, Seek, SeekFrom, Write};
5use std::path::{Component, Path, PathBuf};
6use std::sync::atomic::{AtomicBool, Ordering};
7
8use bzip2::Compression as Bzip2Compression;
9use bzip2::write::BzEncoder;
10use flate2::Compression as ZlibCompression;
11use flate2::write::ZlibEncoder;
12use md5::{Digest, Md5};
13use sha1::Sha1;
14use tempfile::NamedTempFile;
15
16use crate::codepage::encode_header_text;
17use crate::date_time::{
18    format_ewf1_header_date_value, format_ewf1_header2_date_value, format_xheader_date_value,
19};
20use crate::decode::{ChunkEncoding, decode_chunk, validate_encoded_size};
21use crate::format::{ewf1, ewf2};
22use crate::image::Image;
23use crate::types::{
24    AcquisitionError, CompressionLevel, CompressionMethod, CompressionValues, DataChunk,
25    DataChunkEncoding, EncodedDataChunk, EwfMetadata, Format, FormatProfile, HeaderCodepage,
26    HeaderDateFormat, ImageInfo, MediaFlags, MediaType, MemoryExtent, SectorRange,
27    SingleFileAttribute, SingleFileEntry, SingleFileEntryType, SingleFileExtent,
28    SingleFilePermission, SingleFilePermissionGroup, SingleFileSource, SingleFileSubject,
29    SingleFilesAuxTables, SingleFilesInfo, StoredHashes,
30};
31use crate::{EwfError, Result};
32
33const VOLUME_DATA_SIZE: usize = 1052;
34const EWF1_LTREE_HEADER_SIZE: usize = 48;
35const EWF2_DEVICE_INFORMATION_SECTION: u32 = 0x01;
36const EWF2_CASE_DATA_SECTION: u32 = 0x02;
37const EWF2_SECTOR_DATA_SECTION: u32 = 0x03;
38const EWF2_SECTOR_TABLE_SECTION: u32 = 0x04;
39const EWF2_ERROR_TABLE_SECTION: u32 = 0x05;
40const EWF2_SESSION_TABLE_SECTION: u32 = 0x06;
41const EWF2_INCREMENT_DATA_SECTION: u32 = 0x07;
42const EWF2_MD5_HASH_SECTION: u32 = 0x08;
43const EWF2_SHA1_HASH_SECTION: u32 = 0x09;
44const EWF2_RESTART_DATA_SECTION: u32 = 0x0a;
45const EWF2_MEMORY_EXTENTS_TABLE_SECTION: u32 = 0x0c;
46const EWF2_NEXT_SECTION: u32 = 0x0d;
47const EWF2_FINAL_INFORMATION_SECTION: u32 = 0x0e;
48const EWF2_DONE_SECTION: u32 = 0x0f;
49const EWF2_ANALYTICAL_DATA_SECTION: u32 = 0x10;
50const EWF2_SINGLE_FILES_DATA_SECTION: u32 = 0x20;
51const EWF2_SINGLE_FILES_TABLE_SECTION: u32 = 0x21;
52const EWF2_SINGLE_FILES_MD5_HASH_TABLE_SECTION: u32 = 0x22;
53const EWF2_SINGLE_FILES_UNKNOWN_TABLE_SECTION: u32 = 0x23;
54const EWF2_TABLE_HEADER_V2_SIZE: usize = 32;
55const EWF2_TABLE_FOOTER_SIZE: usize = 16;
56const SIGNED_SECTOR_RANGE_MAX: u64 = i64::MAX as u64;
57const EWF2_EXTENDED_ATTRIBUTES_HEADER: &[u8; 37] = &[
58    0x00, 0x00, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x74,
59    0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x62, 0x00, 0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73,
60    0x00, 0x00, 0x00, 0x00, 0x00,
61];
62
63const SINGLE_FILE_ENTRY_TYPES: &[&str] = &[
64    "id", "p", "n", "ls", "lo", "po", "du", "src", "sub", "pm", "cid", "opr", "be", "ha", "sha",
65    "snh", "cr", "wr", "ac", "mo", "dl", "ea",
66];
67const SINGLE_FILE_SOURCE_TYPES: &[&str] = &[
68    "id", "n", "ev", "loc", "gu", "pgu", "dt", "mfr", "mo", "se", "do", "ip", "ma", "tb", "lo",
69    "po", "aq", "ah", "sh",
70];
71const SINGLE_FILE_SUBJECT_TYPES: &[&str] = &["id", "n"];
72const SINGLE_FILE_PERMISSION_TYPES: &[&str] = &["n", "pr", "s", "nta", "nti"];
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75/// Configuration used to create an [`EwfWriter`].
76pub struct WriteOptions {
77    /// Output EWF format.
78    pub format: WriteFormat,
79    /// Number of sectors represented by each chunk.
80    pub sectors_per_chunk: u32,
81    /// Bytes in each logical sector.
82    pub bytes_per_sector: u32,
83    /// Segment set identifier written to output metadata.
84    pub set_identifier: Option<[u8; 16]>,
85    /// Compression method for newly encoded chunks.
86    pub compression: WriteCompression,
87    /// Compression level and flags for newly encoded chunks.
88    pub compression_values: WriteCompressionValues,
89    /// Stored hash values to write.
90    pub hashes: WriteHashes,
91    /// Maximum output segment size in bytes.
92    pub maximum_segment_size: Option<u64>,
93    /// First path of the mirrored secondary output segment set.
94    pub secondary_segment_filename: Option<PathBuf>,
95    /// Declared logical media size in bytes.
96    pub media_size: Option<u64>,
97    /// Case and acquisition metadata to write.
98    pub metadata: EwfMetadata,
99    /// Codepage used to encode EWF1 header values.
100    pub header_codepage: HeaderCodepage,
101    /// Date format used for EWF1 header date values.
102    pub header_values_date_format: HeaderDateFormat,
103    /// Acquisition error ranges to write.
104    pub acquisition_errors: Vec<AcquisitionError>,
105    /// Checksum error ranges to write.
106    pub checksum_errors: Vec<SectorRange>,
107    /// Session sector ranges to write.
108    pub sessions: Vec<SectorRange>,
109    /// Track sector ranges to write.
110    pub tracks: Vec<SectorRange>,
111    /// Media type and acquisition flags to write.
112    pub media_profile: WriteMediaProfile,
113    /// Memory acquisition extents to write.
114    pub memory_extents: Vec<MemoryExtent>,
115    /// Logical single-file catalog to write.
116    pub single_files: Option<SingleFilesInfo>,
117    /// Preserved EWF2 single-file auxiliary table data.
118    pub ewf2_single_files_tables: SingleFilesAuxTables,
119    /// Raw EWF2 increment data sections to write.
120    pub ewf2_increment_data: Vec<Vec<u8>>,
121    /// Raw EWF2 final information section to write.
122    pub ewf2_final_information: Option<Vec<u8>>,
123    /// EWF2 restart data text to write.
124    pub ewf2_restart_data: Option<String>,
125    /// EWF2 analytical data text to write.
126    pub ewf2_analytical_data: Option<String>,
127}
128
129impl Default for WriteOptions {
130    fn default() -> Self {
131        Self {
132            format: WriteFormat::Ewf1Physical,
133            sectors_per_chunk: 64,
134            bytes_per_sector: 512,
135            set_identifier: None,
136            compression: WriteCompression::None,
137            compression_values: WriteCompressionValues::default(),
138            hashes: WriteHashes::default(),
139            maximum_segment_size: None,
140            secondary_segment_filename: None,
141            media_size: None,
142            metadata: EwfMetadata::default(),
143            header_codepage: HeaderCodepage::Ascii,
144            header_values_date_format: HeaderDateFormat::Ctime,
145            acquisition_errors: Vec::new(),
146            checksum_errors: Vec::new(),
147            sessions: Vec::new(),
148            tracks: Vec::new(),
149            media_profile: WriteMediaProfile::default(),
150            memory_extents: Vec::new(),
151            single_files: None,
152            ewf2_single_files_tables: SingleFilesAuxTables::default(),
153            ewf2_increment_data: Vec::new(),
154            ewf2_final_information: None,
155            ewf2_restart_data: None,
156            ewf2_analytical_data: None,
157        }
158    }
159}
160
161impl WriteOptions {
162    /// Copies media geometry and flags from an opened image.
163    ///
164    /// # Errors
165    ///
166    /// Returns an error if the source image contains media values that cannot be
167    /// represented by writer options.
168    pub fn copy_media_values_from_image(&mut self, image: &Image) -> Result<()> {
169        self.copy_media_values_from_info(image.info())
170    }
171
172    /// Copies media geometry and flags from parsed image metadata.
173    ///
174    /// # Errors
175    ///
176    /// Returns an error if the source metadata contains media values that cannot
177    /// be represented by writer options.
178    pub fn copy_media_values_from_info(&mut self, info: &ImageInfo) -> Result<()> {
179        let mut candidate = self.clone();
180        candidate.format = write_format_from_image_info(info);
181        candidate.sectors_per_chunk =
182            required_media_u32(info.media.sectors_per_chunk, "sectors per chunk")?;
183        candidate.bytes_per_sector =
184            required_media_u32(info.media.bytes_per_sector, "bytes per sector")?;
185        candidate.set_identifier = info.media.set_identifier;
186        candidate.media_size = Some(info.logical_size);
187        candidate.media_profile = WriteMediaProfile {
188            media_type: info.media.media_type,
189            error_granularity: info.media.error_granularity,
190            fastbloc: info.media.media_flags.fastbloc,
191            tableau: info.media.media_flags.tableau,
192        };
193        let has_explicit_compression_values =
194            info.media.compression_values != CompressionValues::default();
195        candidate.compression_values = if has_explicit_compression_values {
196            write_compression_values_from_media_values(info.media.compression_values)?
197        } else {
198            WriteCompressionValues::default()
199        };
200        if let Some(compression_method) = info.media.compression_method {
201            candidate.compression = write_compression_from_media_method(compression_method)?;
202        } else if has_explicit_compression_values
203            && candidate.compression_values.level != WriteCompressionLevel::None
204        {
205            candidate.compression = WriteCompression::Zlib;
206        } else {
207            candidate.compression = WriteCompression::None;
208        }
209        validate_options(&candidate)?;
210        *self = candidate;
211        Ok(())
212    }
213
214    /// Copies header values from an opened image.
215    pub fn copy_header_values_from_image(&mut self, image: &Image) {
216        self.copy_header_values_from_info(image.info());
217    }
218
219    /// Copies header values from parsed image metadata.
220    pub fn copy_header_values_from_info(&mut self, info: &ImageInfo) {
221        self.copy_header_values_from_metadata(&info.metadata);
222    }
223
224    /// Copies header values from metadata.
225    pub fn copy_header_values_from_metadata(&mut self, metadata: &EwfMetadata) {
226        self.metadata.copy_header_values_from(metadata);
227    }
228
229    /// Copies stored hash values from an opened image.
230    pub fn copy_hash_values_from_image(&mut self, image: &Image) {
231        self.copy_hash_values_from_info(image.info());
232    }
233
234    /// Copies stored hash values from parsed image metadata.
235    pub fn copy_hash_values_from_info(&mut self, info: &ImageInfo) {
236        self.copy_hash_values_from_stored_hashes(&info.stored_hashes);
237    }
238
239    /// Copies stored hash values from a hash collection.
240    pub fn copy_hash_values_from_stored_hashes(&mut self, hashes: &StoredHashes) {
241        self.hashes.copy_hash_values_from_stored_hashes(hashes);
242    }
243}
244
245#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
246/// Output EWF format selected for writing.
247pub enum WriteFormat {
248    /// EWF1 physical `.E01` output.
249    #[default]
250    Ewf1Physical,
251    /// EWF1 logical `.L01` output.
252    Ewf1Logical,
253    /// EWF1 SMART `.S01` output.
254    Ewf1Smart,
255    /// EWF2 physical `.Ex01` output.
256    Ewf2Physical,
257    /// EWF2 logical `.Lx01` output.
258    Ewf2Logical,
259}
260
261#[derive(Debug, Clone, Default, PartialEq, Eq)]
262/// Hash values configured for writer output.
263pub struct WriteHashes {
264    /// MD5 hash bytes.
265    pub md5: Option<[u8; 16]>,
266    /// SHA1 hash bytes.
267    pub sha1: Option<[u8; 20]>,
268    /// Hash strings keyed by hash identifier.
269    pub hash_values: BTreeMap<String, String>,
270}
271
272impl WriteHashes {
273    /// Copies stored hash values from an image hash collection.
274    pub fn copy_hash_values_from_stored_hashes(&mut self, hashes: &StoredHashes) {
275        self.md5 = hashes.md5;
276        self.sha1 = hashes.sha1;
277        self.hash_values = hashes.hash_values.clone();
278    }
279
280    /// Returns a hash string by identifier.
281    pub fn hash_value(&self, identifier: &str) -> Option<&str> {
282        self.hash_values.get(identifier).map(String::as_str)
283    }
284
285    /// Sets a hash string by identifier and returns the previous value.
286    ///
287    /// `MD5` and `SHA1` values are also parsed into typed byte arrays.
288    ///
289    /// # Errors
290    ///
291    /// Returns an error if an `MD5` or `SHA1` value is not valid hexadecimal of
292    /// the required length.
293    pub fn set_hash_value(
294        &mut self,
295        identifier: impl Into<String>,
296        value: impl Into<String>,
297    ) -> Result<Option<String>> {
298        let identifier = identifier.into();
299        let value = value.into();
300        if identifier.eq_ignore_ascii_case("MD5") {
301            self.md5 = Some(parse_writer_hash_value("MD5", &value)?);
302        } else if identifier.eq_ignore_ascii_case("SHA1") {
303            self.sha1 = Some(parse_writer_hash_value("SHA1", &value)?);
304        }
305        Ok(self.hash_values.insert(identifier, value))
306    }
307
308    /// Returns the number of configured hash strings.
309    pub fn number_of_hash_values(&self) -> usize {
310        self.hash_values.len()
311    }
312
313    /// Returns a hash identifier by enumeration index.
314    pub fn hash_value_identifier(&self, index: usize) -> Option<&str> {
315        self.hash_values.keys().nth(index).map(String::as_str)
316    }
317}
318
319fn parse_writer_hash_value<const N: usize>(label: &str, value: &str) -> Result<[u8; N]> {
320    if value.len() != N * 2 {
321        return Err(EwfError::Unsupported(format!("invalid {label} hash value")));
322    }
323
324    let mut bytes = [0; N];
325    for (index, pair) in value.as_bytes().chunks_exact(2).enumerate() {
326        let high = writer_hash_nibble(pair[0])
327            .ok_or_else(|| EwfError::Unsupported(format!("invalid {label} hash value")))?;
328        let low = writer_hash_nibble(pair[1])
329            .ok_or_else(|| EwfError::Unsupported(format!("invalid {label} hash value")))?;
330        bytes[index] = (high << 4) | low;
331    }
332    Ok(bytes)
333}
334
335fn writer_hash_nibble(value: u8) -> Option<u8> {
336    match value {
337        b'0'..=b'9' => Some(value - b'0'),
338        b'a'..=b'f' => Some(value - b'a' + 10),
339        b'A'..=b'F' => Some(value - b'A' + 10),
340        _ => None,
341    }
342}
343
344#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
345/// Media type and acquisition flags configured for writer output.
346pub struct WriteMediaProfile {
347    /// Media type to write.
348    pub media_type: Option<MediaType>,
349    /// Error granularity in sectors.
350    pub error_granularity: Option<u64>,
351    /// `FastBloc` acquisition flag.
352    pub fastbloc: bool,
353    /// Tableau acquisition flag.
354    pub tableau: bool,
355}
356
357#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
358/// Compression method for writer output.
359pub enum WriteCompression {
360    /// Store chunks without compression.
361    #[default]
362    None,
363    /// Compress chunks with zlib.
364    Zlib,
365    /// Compress chunks with `BZip2`.
366    Bzip2,
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370/// Compression settings for writer output.
371pub struct WriteCompressionValues {
372    /// Compression level.
373    pub level: WriteCompressionLevel,
374    /// Whether empty-block compression flags should be written.
375    pub empty_block: bool,
376}
377
378impl Default for WriteCompressionValues {
379    fn default() -> Self {
380        Self {
381            level: WriteCompressionLevel::Default,
382            empty_block: true,
383        }
384    }
385}
386
387#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
388/// Compression level for writer output.
389pub enum WriteCompressionLevel {
390    /// Use the compressor default level.
391    #[default]
392    Default,
393    /// Do not compress.
394    None,
395    /// Prefer faster compression.
396    Fast,
397    /// Prefer stronger compression.
398    Best,
399}
400
401#[derive(Debug, Clone, PartialEq, Eq)]
402/// Result returned after finalizing writer output.
403pub struct WriteResult {
404    /// Output segment paths or labels.
405    pub segment_paths: Vec<PathBuf>,
406    /// Mirrored secondary output segment paths.
407    pub secondary_segment_paths: Vec<PathBuf>,
408    /// Logical media size written in bytes.
409    pub logical_size: u64,
410    /// Logical chunk size written in bytes.
411    pub chunk_size: u64,
412    /// Number of logical chunks written.
413    pub chunk_count: u64,
414}
415
416/// Incremental EWF writer.
417///
418/// The writer buffers media data until [`EwfWriter::finish`] or another finish
419/// method is called. Geometry and format options must be configured before
420/// media data is written.
421pub struct EwfWriter {
422    path: PathBuf,
423    options: WriteOptions,
424    chunk_size: u64,
425    chunk_capacity: usize,
426    raw: RawSpool,
427    encoded_chunks: BTreeMap<u64, RememberedEncodedChunk>,
428    current_offset: u64,
429    logical_input_size: u64,
430    abort_signaled: AtomicBool,
431}
432
433struct PreparedWrite {
434    path: PathBuf,
435    options: WriteOptions,
436    chunks: Vec<ChunkDescriptor>,
437    spool: ChunkSpool,
438    logical_size: u64,
439    chunk_size: u64,
440    chunk_count: u64,
441    chunk_count_u32: u32,
442    sector_count: u64,
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446enum TerminalSection {
447    Done,
448    Next,
449}
450
451impl TerminalSection {
452    fn ewf1_type(self) -> &'static [u8; 4] {
453        match self {
454            Self::Done => b"done",
455            Self::Next => b"next",
456        }
457    }
458
459    fn ewf2_type(self) -> u32 {
460        match self {
461            Self::Done => EWF2_DONE_SECTION,
462            Self::Next => EWF2_NEXT_SECTION,
463        }
464    }
465}
466
467impl std::fmt::Debug for EwfWriter {
468    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469        formatter
470            .debug_struct("EwfWriter")
471            .field("path", &self.path)
472            .field("options", &self.options)
473            .field("chunk_size", &self.chunk_size)
474            .field("chunk_capacity", &self.chunk_capacity)
475            .field("raw", &"<temporary>")
476            .field("raw_spooled_bytes", &self.raw.len())
477            .field("encoded_chunks", &self.encoded_chunks.len())
478            .field("current_offset", &self.current_offset)
479            .field("logical_input_size", &self.logical_input_size)
480            .field(
481                "abort_signaled",
482                &self.abort_signaled.load(Ordering::Relaxed),
483            )
484            .finish()
485    }
486}
487
488impl EwfWriter {
489    /// Creates a new writer for a file-backed EWF segment set.
490    ///
491    /// # Errors
492    ///
493    /// Returns an error if options are invalid, the secondary segment filename
494    /// conflicts with the primary filename, or temporary spool creation fails.
495    pub fn create(path: impl AsRef<Path>, mut options: WriteOptions) -> Result<Self> {
496        let path = path.as_ref().to_path_buf();
497        options.maximum_segment_size = normalize_maximum_segment_size(options.maximum_segment_size);
498        options.media_size = normalize_media_size(options.media_size);
499        validate_options(&options)?;
500        validate_secondary_segment_filename(&path, options.secondary_segment_filename.as_deref())?;
501        let (chunk_size, chunk_capacity) =
502            writer_chunk_geometry(options.sectors_per_chunk, options.bytes_per_sector)?;
503
504        Ok(Self {
505            path,
506            options,
507            chunk_size,
508            chunk_capacity,
509            raw: RawSpool::new()?,
510            encoded_chunks: BTreeMap::new(),
511            current_offset: 0,
512            logical_input_size: 0,
513            abort_signaled: AtomicBool::new(false),
514        })
515    }
516
517    /// Creates a writer initialized from an existing image and copies its media.
518    ///
519    /// # Errors
520    ///
521    /// Returns an error if the source image cannot be represented by the writer,
522    /// media copying fails, or writer creation fails.
523    pub fn create_from_image(path: impl AsRef<Path>, image: &Image) -> Result<Self> {
524        let options = rewrite_options_from_image_info(image.info())?;
525        let mut writer = Self::create(path, options)?;
526        copy_image_media_to_writer(image, &mut writer)?;
527        Ok(writer)
528    }
529
530    /// Opens an existing image and prepares a writer that can append or rewrite it.
531    ///
532    /// # Errors
533    ///
534    /// Returns an error if the existing image cannot be opened, copied, or
535    /// represented by writer options.
536    pub fn resume<P: AsRef<Path>>(path: P) -> Result<Self> {
537        let path = path.as_ref().to_path_buf();
538        let image = Image::open(&path)?;
539        let mut writer = Self::create_from_image(&path, &image)?;
540        writer.options.media_size = None;
541        Ok(writer)
542    }
543
544    /// Returns the first output segment filename.
545    pub fn filename(&self) -> &Path {
546        self.path.as_path()
547    }
548
549    /// Returns the first output segment filename.
550    pub fn segment_filename(&self) -> &Path {
551        self.path.as_path()
552    }
553
554    /// Sets the first output segment filename.
555    pub fn set_segment_filename(&mut self, path: impl AsRef<Path>) {
556        self.path = path.as_ref().to_path_buf();
557    }
558
559    /// Returns the first mirrored secondary output segment filename.
560    pub fn secondary_segment_filename(&self) -> Option<&Path> {
561        self.options.secondary_segment_filename.as_deref()
562    }
563
564    /// Sets the first mirrored secondary output segment filename.
565    ///
566    /// # Errors
567    ///
568    /// Returns an error if the secondary filename conflicts with the primary
569    /// output filename.
570    pub fn set_secondary_segment_filename(&mut self, path: impl AsRef<Path>) -> Result<()> {
571        let path = path.as_ref().to_path_buf();
572        validate_secondary_segment_filename(&self.path, Some(&path))?;
573        self.options.secondary_segment_filename = Some(path);
574        Ok(())
575    }
576
577    /// Clears mirrored secondary output.
578    pub fn clear_secondary_segment_filename(&mut self) {
579        self.options.secondary_segment_filename = None;
580    }
581
582    /// Writes all bytes at the current writer position.
583    ///
584    /// # Errors
585    ///
586    /// Returns an error if the writer has been aborted or writing would exceed
587    /// configured limits.
588    pub fn write_all(&mut self, data: &[u8]) -> Result<()> {
589        self.ensure_not_aborted()?;
590        self.write_to_current(data)?;
591        Ok(())
592    }
593
594    /// Writes bytes at the current writer position and returns the byte count.
595    ///
596    /// # Errors
597    ///
598    /// Returns an error if the writer has been aborted or writing would exceed
599    /// configured limits.
600    pub fn write_buffer(&mut self, data: &[u8]) -> Result<usize> {
601        self.ensure_not_aborted()?;
602        self.write_to_current(data)
603    }
604
605    /// Writes bytes at an absolute logical byte offset.
606    ///
607    /// # Errors
608    ///
609    /// Returns an error if the writer has been aborted or writing would exceed
610    /// configured limits.
611    pub fn write_at(&mut self, data: &[u8], offset: u64) -> Result<usize> {
612        self.ensure_not_aborted()?;
613        self.current_offset = offset;
614        self.write_to_current(data)
615    }
616
617    /// Alias for [`EwfWriter::write_at`].
618    ///
619    /// # Errors
620    ///
621    /// Returns the same errors as [`EwfWriter::write_at`].
622    pub fn write_buffer_at_offset(&mut self, data: &[u8], offset: u64) -> Result<usize> {
623        self.write_at(data, offset)
624    }
625
626    /// Writes a decoded data chunk at the current writer position.
627    ///
628    /// # Errors
629    ///
630    /// Returns an error if the chunk metadata is inconsistent, the writer has
631    /// been aborted, or writing would exceed configured limits.
632    pub fn write_data_chunk(&mut self, chunk: &DataChunk) -> Result<usize> {
633        self.ensure_not_aborted()?;
634        validate_write_data_chunk(chunk)?;
635        self.write_to_current(&chunk.data)
636    }
637
638    /// Decodes and writes an encoded data chunk at the current writer position.
639    ///
640    /// # Errors
641    ///
642    /// Returns an error if the encoded chunk cannot be decoded, the writer has
643    /// been aborted, or writing would exceed configured limits.
644    pub fn write_encoded_data_chunk(&mut self, chunk: &EncodedDataChunk) -> Result<usize> {
645        self.ensure_not_aborted()?;
646        let data = decode_write_encoded_data_chunk(chunk)?;
647        let offset = self.current_offset;
648        let written = self.write_to_current(&data)?;
649        self.remember_encoded_data_chunk_at(chunk, offset, data.len());
650        Ok(written)
651    }
652
653    /// Writes a decoded data chunk at an absolute logical byte offset.
654    ///
655    /// # Errors
656    ///
657    /// Returns the same errors as [`EwfWriter::write_data_chunk`] and
658    /// [`EwfWriter::write_at`].
659    pub fn write_data_chunk_at(&mut self, chunk: &DataChunk, offset: u64) -> Result<usize> {
660        self.ensure_not_aborted()?;
661        validate_write_data_chunk(chunk)?;
662        self.write_at(&chunk.data, offset)
663    }
664
665    /// Decodes and writes an encoded data chunk at an absolute logical byte offset.
666    ///
667    /// # Errors
668    ///
669    /// Returns the same errors as [`EwfWriter::write_encoded_data_chunk`] and
670    /// [`EwfWriter::write_at`].
671    pub fn write_encoded_data_chunk_at(
672        &mut self,
673        chunk: &EncodedDataChunk,
674        offset: u64,
675    ) -> Result<usize> {
676        self.ensure_not_aborted()?;
677        let data = decode_write_encoded_data_chunk(chunk)?;
678        self.current_offset = offset;
679        let written = self.write_to_current(&data)?;
680        self.remember_encoded_data_chunk_at(chunk, offset, data.len());
681        Ok(written)
682    }
683
684    /// Returns the configured output format.
685    pub fn format(&self) -> WriteFormat {
686        self.options.format
687    }
688
689    /// Sets the output format before media data is written.
690    ///
691    /// # Errors
692    ///
693    /// Returns an error if media data has already been written or the new option
694    /// combination is unsupported.
695    pub fn set_format(&mut self, format: WriteFormat) -> Result<()> {
696        self.ensure_configuration_mutable("format")?;
697        let mut options = self.options.clone();
698        options.format = format;
699        validate_options(&options)?;
700        self.options = options;
701        Ok(())
702    }
703
704    /// Returns the current logical byte write position.
705    pub fn position(&self) -> u64 {
706        self.current_offset
707    }
708
709    /// Returns the current logical chunk size in bytes.
710    pub fn chunk_size(&self) -> u64 {
711        self.chunk_size
712    }
713
714    /// Returns the configured sectors per chunk.
715    pub fn sectors_per_chunk(&self) -> u32 {
716        self.options.sectors_per_chunk
717    }
718
719    /// Sets sectors per chunk before media data is written.
720    ///
721    /// # Errors
722    ///
723    /// Returns an error if media data has already been written or the resulting
724    /// chunk geometry is invalid.
725    pub fn set_sectors_per_chunk(&mut self, sectors_per_chunk: u32) -> Result<()> {
726        self.ensure_configuration_mutable("sectors per chunk")?;
727        let (chunk_size, chunk_capacity) =
728            writer_chunk_geometry(sectors_per_chunk, self.options.bytes_per_sector)?;
729        self.options.sectors_per_chunk = sectors_per_chunk;
730        self.chunk_size = chunk_size;
731        self.chunk_capacity = chunk_capacity;
732        self.encoded_chunks.clear();
733        Ok(())
734    }
735
736    /// Returns the configured bytes per sector.
737    pub fn bytes_per_sector(&self) -> u32 {
738        self.options.bytes_per_sector
739    }
740
741    /// Sets bytes per sector before media data is written.
742    ///
743    /// # Errors
744    ///
745    /// Returns an error if media data has already been written or the resulting
746    /// chunk geometry is invalid.
747    pub fn set_bytes_per_sector(&mut self, bytes_per_sector: u32) -> Result<()> {
748        self.ensure_configuration_mutable("bytes per sector")?;
749        let (chunk_size, chunk_capacity) =
750            writer_chunk_geometry(self.options.sectors_per_chunk, bytes_per_sector)?;
751        self.options.bytes_per_sector = bytes_per_sector;
752        self.chunk_size = chunk_size;
753        self.chunk_capacity = chunk_capacity;
754        self.encoded_chunks.clear();
755        Ok(())
756    }
757
758    /// Returns the logical size that would be written after sector padding.
759    ///
760    /// # Errors
761    ///
762    /// Returns an error if configured sizes overflow or are inconsistent.
763    pub fn logical_size(&self) -> Result<u64> {
764        padded_logical_size(
765            target_media_input_size(self.logical_input_size, self.options.media_size)?,
766            self.options.bytes_per_sector,
767        )
768    }
769
770    /// Alias for [`EwfWriter::logical_size`].
771    ///
772    /// # Errors
773    ///
774    /// Returns the same errors as [`EwfWriter::logical_size`].
775    pub fn media_size(&self) -> Result<u64> {
776        self.logical_size()
777    }
778
779    /// Returns the logical sector count that would be written.
780    ///
781    /// # Errors
782    ///
783    /// Returns an error if the logical size cannot be computed.
784    pub fn number_of_sectors(&self) -> Result<u64> {
785        Ok(self.media_size()? / u64::from(self.options.bytes_per_sector))
786    }
787
788    /// Returns the configured maximum segment size in bytes.
789    pub fn maximum_segment_size(&self) -> Option<u64> {
790        self.options.maximum_segment_size
791    }
792
793    /// Sets the maximum segment size before media data is written.
794    ///
795    /// # Errors
796    ///
797    /// Returns an error if media data has already been written or the value is
798    /// invalid for the current output format.
799    pub fn set_maximum_segment_size(&mut self, maximum_segment_size: Option<u64>) -> Result<()> {
800        self.ensure_configuration_mutable("maximum segment size")?;
801        let mut options = self.options.clone();
802        options.maximum_segment_size = normalize_maximum_segment_size(maximum_segment_size);
803        validate_options(&options)?;
804        self.options = options;
805        Ok(())
806    }
807
808    /// Sets the declared logical media size before media data is written.
809    ///
810    /// # Errors
811    ///
812    /// Returns an error if media data has already been written or the requested
813    /// size is smaller than data already written.
814    pub fn set_media_size(&mut self, media_size: u64) -> Result<()> {
815        self.ensure_configuration_mutable("media size")?;
816        let media_size = normalize_media_size(Some(media_size));
817        if media_size.is_some_and(|media_size| self.logical_input_size > media_size) {
818            return Err(EwfError::Unsupported(
819                "writer configured media size is smaller than written data".into(),
820            ));
821        }
822        self.options.media_size = media_size;
823        Ok(())
824    }
825
826    /// Returns the number of logical chunks that would be written.
827    ///
828    /// # Errors
829    ///
830    /// Returns an error if the logical size cannot be computed.
831    pub fn number_of_chunks_written(&self) -> Result<u64> {
832        Ok(self.logical_size()?.div_ceil(self.chunk_size))
833    }
834
835    /// Returns the configured segment set identifier.
836    pub fn segment_file_set_identifier(&self) -> Option<[u8; 16]> {
837        self.options.set_identifier
838    }
839
840    /// Signals future writer operations to abort with [`EwfError::Aborted`].
841    pub fn signal_abort(&self) {
842        self.abort_signaled.store(true, Ordering::Relaxed);
843    }
844
845    /// Sets the segment set identifier before media data is written.
846    ///
847    /// # Errors
848    ///
849    /// Returns an error if media data has already been written.
850    pub fn set_segment_file_set_identifier(&mut self, set_identifier: [u8; 16]) -> Result<()> {
851        self.ensure_configuration_mutable("set identifier")?;
852        self.options.set_identifier = Some(set_identifier);
853        Ok(())
854    }
855
856    /// Returns the configured compression method.
857    pub fn compression_method(&self) -> WriteCompression {
858        self.options.compression
859    }
860
861    /// Sets the compression method before media data is written.
862    ///
863    /// # Errors
864    ///
865    /// Returns an error if media data has already been written or `BZip2` is
866    /// selected for an EWF1 output format.
867    pub fn set_compression_method(&mut self, compression: WriteCompression) -> Result<()> {
868        self.ensure_configuration_mutable("compression method")?;
869        if matches!(compression, WriteCompression::Bzip2) && !is_ewf2_format(self.options.format) {
870            return Err(EwfError::Unsupported(
871                "BZip2 writer compression is only supported for EWF2".into(),
872            ));
873        }
874        self.options.compression = compression;
875        Ok(())
876    }
877
878    /// Returns the configured compression values.
879    pub fn compression_values(&self) -> WriteCompressionValues {
880        self.options.compression_values
881    }
882
883    /// Sets compression values before media data is written.
884    ///
885    /// # Errors
886    ///
887    /// Returns an error if media data has already been written.
888    pub fn set_compression_values(
889        &mut self,
890        compression_values: WriteCompressionValues,
891    ) -> Result<()> {
892        self.ensure_configuration_mutable("compression values")?;
893        self.options.compression_values = compression_values;
894        Ok(())
895    }
896
897    /// Returns the configured media type.
898    pub fn media_type(&self) -> Option<MediaType> {
899        self.options.media_profile.media_type
900    }
901
902    /// Sets the media type before media data is written.
903    ///
904    /// # Errors
905    ///
906    /// Returns an error if media data has already been written.
907    pub fn set_media_type(&mut self, media_type: Option<MediaType>) -> Result<()> {
908        self.ensure_configuration_mutable("media type")?;
909        self.options.media_profile.media_type = media_type;
910        Ok(())
911    }
912
913    /// Returns the configured error granularity in sectors.
914    pub fn error_granularity(&self) -> Option<u64> {
915        self.options.media_profile.error_granularity
916    }
917
918    /// Sets error granularity before media data is written.
919    ///
920    /// # Errors
921    ///
922    /// Returns an error if media data has already been written.
923    pub fn set_error_granularity(&mut self, error_granularity: Option<u64>) -> Result<()> {
924        self.ensure_configuration_mutable("error granularity")?;
925        self.options.media_profile.error_granularity = error_granularity;
926        Ok(())
927    }
928
929    /// Returns media flags derived from format and media profile options.
930    pub fn media_flags(&self) -> MediaFlags {
931        writer_media_flags(self.options.format, self.options.media_profile)
932    }
933
934    /// Sets writable media flags before media data is written.
935    ///
936    /// The physical flag is controlled by [`WriteFormat`] and cannot be changed
937    /// independently.
938    ///
939    /// # Errors
940    ///
941    /// Returns an error if media data has already been written or if the
942    /// physical flag conflicts with the output format.
943    pub fn set_media_flags(&mut self, media_flags: MediaFlags) -> Result<()> {
944        self.ensure_configuration_mutable("media flags")?;
945        if media_flags.physical != write_format_is_physical(self.options.format) {
946            return Err(EwfError::Unsupported(
947                "writer physical media flag is controlled by image format".into(),
948            ));
949        }
950        self.options.media_profile.fastbloc = media_flags.fastbloc;
951        self.options.media_profile.tableau = media_flags.tableau;
952        Ok(())
953    }
954
955    /// Copies media geometry and flags from an opened image.
956    ///
957    /// # Errors
958    ///
959    /// Returns an error if the source values cannot be represented or conflict
960    /// with media data already written.
961    pub fn copy_media_values_from_image(&mut self, image: &Image) -> Result<()> {
962        self.copy_media_values_from_info(image.info())
963    }
964
965    /// Copies media geometry and flags from parsed image metadata.
966    ///
967    /// # Errors
968    ///
969    /// Returns an error if the source values cannot be represented or conflict
970    /// with media data already written.
971    pub fn copy_media_values_from_info(&mut self, info: &ImageInfo) -> Result<()> {
972        let mut options = self.options.clone();
973        options.copy_media_values_from_info(info)?;
974        if options
975            .media_size
976            .is_some_and(|media_size| self.logical_input_size > media_size)
977        {
978            return Err(EwfError::Unsupported(
979                "writer configured media size is smaller than written data".into(),
980            ));
981        }
982        let (chunk_size, chunk_capacity) =
983            writer_chunk_geometry(options.sectors_per_chunk, options.bytes_per_sector)?;
984        self.options = options;
985        self.chunk_size = chunk_size;
986        self.chunk_capacity = chunk_capacity;
987        Ok(())
988    }
989
990    /// Returns a configured header value by EWF identifier.
991    pub fn header_value(&self, identifier: &str) -> Option<std::borrow::Cow<'_, str>> {
992        self.options
993            .metadata
994            .header_value_with_date_format(identifier, self.options.header_values_date_format)
995    }
996
997    /// Returns the configured header codepage.
998    pub fn header_codepage(&self) -> HeaderCodepage {
999        self.options.header_codepage
1000    }
1001
1002    /// Sets the header codepage.
1003    pub fn set_header_codepage(&mut self, header_codepage: HeaderCodepage) {
1004        self.options.header_codepage = header_codepage;
1005    }
1006
1007    /// Returns the configured header date format.
1008    pub fn header_values_date_format(&self) -> HeaderDateFormat {
1009        self.options.header_values_date_format
1010    }
1011
1012    /// Sets the header date format.
1013    pub fn set_header_values_date_format(&mut self, date_format: HeaderDateFormat) {
1014        self.options.header_values_date_format = date_format;
1015    }
1016
1017    /// Copies header values from an opened image.
1018    pub fn copy_header_values_from_image(&mut self, image: &Image) {
1019        self.copy_header_values_from_info(image.info());
1020    }
1021
1022    /// Copies header values from parsed image metadata.
1023    pub fn copy_header_values_from_info(&mut self, info: &ImageInfo) {
1024        self.copy_header_values_from_metadata(&info.metadata);
1025    }
1026
1027    /// Copies header values from metadata.
1028    pub fn copy_header_values_from_metadata(&mut self, metadata: &EwfMetadata) {
1029        self.options.copy_header_values_from_metadata(metadata);
1030    }
1031
1032    /// Copies stored hash values from an opened image.
1033    pub fn copy_hash_values_from_image(&mut self, image: &Image) {
1034        self.copy_hash_values_from_info(image.info());
1035    }
1036
1037    /// Copies stored hash values from parsed image metadata.
1038    pub fn copy_hash_values_from_info(&mut self, info: &ImageInfo) {
1039        self.copy_hash_values_from_stored_hashes(&info.stored_hashes);
1040    }
1041
1042    /// Copies stored hash values from a hash collection.
1043    pub fn copy_hash_values_from_stored_hashes(&mut self, hashes: &StoredHashes) {
1044        self.options.copy_hash_values_from_stored_hashes(hashes);
1045    }
1046
1047    /// Sets a header value by EWF identifier and returns the previous value.
1048    pub fn set_header_value(
1049        &mut self,
1050        identifier: &str,
1051        value: impl Into<String>,
1052    ) -> Option<String> {
1053        self.options.metadata.set_header_value(identifier, value)
1054    }
1055
1056    /// Returns the number of configured header values.
1057    pub fn number_of_header_values(&self) -> usize {
1058        self.options.metadata.number_of_header_values()
1059    }
1060
1061    /// Returns a configured header value identifier by enumeration index.
1062    pub fn header_value_identifier(&self, index: usize) -> Option<&str> {
1063        self.options.metadata.header_value_identifier(index)
1064    }
1065
1066    /// Returns a configured hash string by identifier.
1067    pub fn hash_value(&self, identifier: &str) -> Option<&str> {
1068        self.options.hashes.hash_value(identifier)
1069    }
1070
1071    /// Sets a configured hash string by identifier and returns the previous value.
1072    ///
1073    /// # Errors
1074    ///
1075    /// Returns an error if an `MD5` or `SHA1` value is not valid hexadecimal of
1076    /// the required length.
1077    pub fn set_hash_value(
1078        &mut self,
1079        identifier: impl Into<String>,
1080        value: impl Into<String>,
1081    ) -> Result<Option<String>> {
1082        self.options.hashes.set_hash_value(identifier, value)
1083    }
1084
1085    /// Returns the number of configured hash strings.
1086    pub fn number_of_hash_values(&self) -> usize {
1087        self.options.hashes.number_of_hash_values()
1088    }
1089
1090    /// Returns a configured hash identifier by enumeration index.
1091    pub fn hash_value_identifier(&self, index: usize) -> Option<&str> {
1092        self.options.hashes.hash_value_identifier(index)
1093    }
1094
1095    /// Returns the configured MD5 hash bytes.
1096    pub fn md5_hash(&self) -> Option<[u8; 16]> {
1097        self.options.hashes.md5
1098    }
1099
1100    /// Sets the configured MD5 hash bytes.
1101    ///
1102    /// # Errors
1103    ///
1104    /// Returns an error if an MD5 hash is already configured.
1105    pub fn set_md5_hash(&mut self, md5: [u8; 16]) -> Result<()> {
1106        if self.options.hashes.md5.is_some() {
1107            return Err(EwfError::Unsupported("MD5 hash cannot be changed".into()));
1108        }
1109        self.options.hashes.md5 = Some(md5);
1110        self.options
1111            .hashes
1112            .hash_values
1113            .entry("MD5".to_string())
1114            .or_insert_with(|| hex_string(&md5));
1115        Ok(())
1116    }
1117
1118    /// Returns the configured SHA1 hash bytes.
1119    pub fn sha1_hash(&self) -> Option<[u8; 20]> {
1120        self.options.hashes.sha1
1121    }
1122
1123    /// Sets the configured SHA1 hash bytes.
1124    ///
1125    /// # Errors
1126    ///
1127    /// Returns an error if a SHA1 hash is already configured.
1128    pub fn set_sha1_hash(&mut self, sha1: [u8; 20]) -> Result<()> {
1129        if self.options.hashes.sha1.is_some() {
1130            return Err(EwfError::Unsupported("SHA1 hash cannot be changed".into()));
1131        }
1132        self.options.hashes.sha1 = Some(sha1);
1133        self.options
1134            .hashes
1135            .hash_values
1136            .entry("SHA1".to_string())
1137            .or_insert_with(|| hex_string(&sha1));
1138        Ok(())
1139    }
1140
1141    /// Returns configured acquisition error ranges.
1142    pub fn acquisition_errors(&self) -> &[AcquisitionError] {
1143        &self.options.acquisition_errors
1144    }
1145
1146    /// Returns the number of configured acquisition error ranges.
1147    pub fn number_of_acquisition_errors(&self) -> usize {
1148        self.options.acquisition_errors.len()
1149    }
1150
1151    /// Returns a configured acquisition error range by index.
1152    pub fn acquisition_error(&self, index: usize) -> Option<&AcquisitionError> {
1153        self.options.acquisition_errors.get(index)
1154    }
1155
1156    /// Appends an acquisition error range.
1157    ///
1158    /// # Errors
1159    ///
1160    /// This method currently validates no additional constraints and returns
1161    /// `Ok(())` after appending.
1162    pub fn append_acquisition_error(&mut self, first_sector: u64, sector_count: u64) -> Result<()> {
1163        self.options.acquisition_errors.push(AcquisitionError {
1164            first_sector,
1165            sector_count,
1166        });
1167        Ok(())
1168    }
1169
1170    /// Returns configured checksum error ranges.
1171    pub fn checksum_errors(&self) -> &[SectorRange] {
1172        &self.options.checksum_errors
1173    }
1174
1175    /// Returns the number of configured checksum error ranges.
1176    pub fn number_of_checksum_errors(&self) -> usize {
1177        self.options.checksum_errors.len()
1178    }
1179
1180    /// Returns a configured checksum error range by index.
1181    pub fn checksum_error(&self, index: usize) -> Option<&SectorRange> {
1182        self.options.checksum_errors.get(index)
1183    }
1184
1185    /// Appends a checksum error range.
1186    ///
1187    /// # Errors
1188    ///
1189    /// This method currently validates no additional constraints and returns
1190    /// `Ok(())` after appending.
1191    pub fn append_checksum_error(&mut self, first_sector: u64, sector_count: u64) -> Result<()> {
1192        self.options.checksum_errors.push(SectorRange {
1193            first_sector,
1194            sector_count,
1195        });
1196        Ok(())
1197    }
1198
1199    /// Returns configured session sector ranges.
1200    pub fn sessions(&self) -> &[SectorRange] {
1201        &self.options.sessions
1202    }
1203
1204    /// Returns the number of configured session sector ranges.
1205    pub fn number_of_sessions(&self) -> usize {
1206        self.options.sessions.len()
1207    }
1208
1209    /// Returns a configured session sector range by index.
1210    pub fn session(&self, index: usize) -> Option<&SectorRange> {
1211        self.options.sessions.get(index)
1212    }
1213
1214    /// Appends a session sector range.
1215    ///
1216    /// # Errors
1217    ///
1218    /// Returns an error if the range values exceed the signed 64-bit range
1219    /// required by EWF metadata.
1220    pub fn append_session(&mut self, first_sector: u64, sector_count: u64) -> Result<()> {
1221        validate_signed_sector_range_value("session", "start sector", first_sector)?;
1222        validate_signed_sector_range_value("session", "sector count", sector_count)?;
1223        self.options.sessions.push(SectorRange {
1224            first_sector,
1225            sector_count,
1226        });
1227        Ok(())
1228    }
1229
1230    /// Returns configured track sector ranges.
1231    pub fn tracks(&self) -> &[SectorRange] {
1232        &self.options.tracks
1233    }
1234
1235    /// Returns the number of configured track sector ranges.
1236    pub fn number_of_tracks(&self) -> usize {
1237        self.options.tracks.len()
1238    }
1239
1240    /// Returns a configured track sector range by index.
1241    pub fn track(&self, index: usize) -> Option<&SectorRange> {
1242        self.options.tracks.get(index)
1243    }
1244
1245    /// Appends a track sector range.
1246    ///
1247    /// # Errors
1248    ///
1249    /// Returns an error if the range values exceed the signed 64-bit range
1250    /// required by EWF metadata.
1251    pub fn append_track(&mut self, first_sector: u64, sector_count: u64) -> Result<()> {
1252        validate_signed_sector_range_value("track", "start sector", first_sector)?;
1253        validate_signed_sector_range_value("track", "sector count", sector_count)?;
1254        self.options.tracks.push(SectorRange {
1255            first_sector,
1256            sector_count,
1257        });
1258        Ok(())
1259    }
1260
1261    /// Returns the current logical byte write position.
1262    pub fn offset(&self) -> u64 {
1263        self.current_offset
1264    }
1265
1266    /// Seeks the writer position and returns the new position.
1267    ///
1268    /// # Errors
1269    ///
1270    /// Returns an error if the seek would move before the start of the media or
1271    /// beyond representable bounds.
1272    pub fn seek_offset(&mut self, position: SeekFrom) -> Result<u64> {
1273        self.seek_position(position)
1274    }
1275
1276    /// Seeks the writer position and returns the new position.
1277    ///
1278    /// # Errors
1279    ///
1280    /// Returns an error if the seek would move before the start of the media or
1281    /// beyond representable bounds.
1282    pub fn seek_position(&mut self, position: SeekFrom) -> Result<u64> {
1283        let end_size = target_media_input_size(self.logical_input_size, self.options.media_size)?;
1284        let next = checked_writer_seek(self.current_offset, end_size, position)?;
1285        self.current_offset = next;
1286        Ok(next)
1287    }
1288
1289    /// Finalizes a complete image and writes a terminal `done` marker.
1290    ///
1291    /// # Errors
1292    ///
1293    /// Returns an error if output cannot be prepared, encoded, written, flushed,
1294    /// or mirrored.
1295    pub fn finish(self) -> Result<WriteResult> {
1296        self.finish_with_terminal_section(TerminalSection::Done)
1297    }
1298
1299    /// Finalizes an incomplete image with a continuation marker.
1300    ///
1301    /// # Errors
1302    ///
1303    /// Returns an error if output cannot be prepared, encoded, written, flushed,
1304    /// or mirrored.
1305    pub fn finish_incomplete(self) -> Result<WriteResult> {
1306        self.finish_with_terminal_section(TerminalSection::Next)
1307    }
1308
1309    fn finish_with_terminal_section(self, terminal: TerminalSection) -> Result<WriteResult> {
1310        let PreparedWrite {
1311            path,
1312            options,
1313            chunks,
1314            mut spool,
1315            logical_size,
1316            chunk_size,
1317            chunk_count,
1318            chunk_count_u32,
1319            sector_count,
1320        } = self.prepare_write()?;
1321
1322        if is_ewf2_format(options.format) {
1323            let groups = segment_groups(
1324                chunks,
1325                options.maximum_segment_size,
1326                &options,
1327                sector_count,
1328                u64::from(chunk_count_u32),
1329            )?;
1330            let group_count = groups.len();
1331            let mut segment_paths = Vec::with_capacity(group_count);
1332            let mut first_chunk = 0_u64;
1333            for (index, group) in groups.into_iter().enumerate() {
1334                let segment_number = u32::try_from(index + 1).map_err(|_| {
1335                    EwfError::Unsupported("EWF2 writer segment count exceeds u32".into())
1336                })?;
1337                let terminal_section_type = if index + 1 == group_count {
1338                    terminal.ewf2_type()
1339                } else {
1340                    EWF2_NEXT_SECTION
1341                };
1342                let segment_path = ewf2_segment_path(&path, index + 1)?;
1343                let mut file = File::create(&segment_path)?;
1344                write_ewf2_segment(
1345                    &mut file,
1346                    &mut spool,
1347                    &group,
1348                    &options,
1349                    Ewf2SegmentWriteContext {
1350                        segment_number,
1351                        first_chunk,
1352                        total_chunk_count: chunk_count_u32,
1353                        sector_count,
1354                        terminal_section_type,
1355                    },
1356                )?;
1357                file.flush()?;
1358                first_chunk = first_chunk
1359                    .checked_add(u64::try_from(group.len()).expect("usize fits u64"))
1360                    .ok_or_else(|| {
1361                        EwfError::Malformed("writer EWF2 first chunk overflow".into())
1362                    })?;
1363                segment_paths.push(segment_path);
1364            }
1365            remove_stale_segment_files(&path, group_count, true)?;
1366            let secondary_segment_paths = mirror_secondary_segment_files(
1367                &path,
1368                options.secondary_segment_filename.as_deref(),
1369                &segment_paths,
1370                group_count,
1371                true,
1372            )?;
1373
1374            return Ok(WriteResult {
1375                segment_paths,
1376                secondary_segment_paths,
1377                logical_size,
1378                chunk_size,
1379                chunk_count,
1380            });
1381        }
1382
1383        let groups = segment_groups(
1384            chunks,
1385            options.maximum_segment_size,
1386            &options,
1387            sector_count,
1388            u64::from(chunk_count_u32),
1389        )?;
1390        let group_count = groups.len();
1391        let mut segment_paths = Vec::with_capacity(group_count);
1392        for (index, group) in groups.into_iter().enumerate() {
1393            let segment_number = u16::try_from(index + 1).map_err(|_| {
1394                EwfError::Unsupported("EWF1 writer segment count exceeds u16".into())
1395            })?;
1396            let is_last = index + 1 == group_count;
1397            let segment_terminal = if is_last {
1398                terminal
1399            } else {
1400                TerminalSection::Done
1401            };
1402            let sections = Ewf1SegmentSections::for_segment(
1403                index == 0,
1404                is_last && terminal == TerminalSection::Done,
1405            );
1406            let segment_path = segment_path(&path, index + 1)?;
1407            let mut file = File::create(&segment_path)?;
1408            write_ewf1_segment(
1409                &mut file,
1410                &mut spool,
1411                &group,
1412                &options,
1413                Ewf1SegmentWriteContext {
1414                    segment_number,
1415                    chunk_count: chunk_count_u32,
1416                    sector_count,
1417                    sections,
1418                    terminal_section: segment_terminal,
1419                },
1420            )?;
1421            file.flush()?;
1422            segment_paths.push(segment_path);
1423        }
1424        remove_stale_segment_files(&path, group_count, false)?;
1425        let secondary_segment_paths = mirror_secondary_segment_files(
1426            &path,
1427            options.secondary_segment_filename.as_deref(),
1428            &segment_paths,
1429            group_count,
1430            false,
1431        )?;
1432
1433        Ok(WriteResult {
1434            segment_paths,
1435            secondary_segment_paths,
1436            logical_size,
1437            chunk_size,
1438            chunk_count,
1439        })
1440    }
1441
1442    /// Alias for [`EwfWriter::finish`].
1443    ///
1444    /// # Errors
1445    ///
1446    /// Returns the same errors as [`EwfWriter::finish`].
1447    pub fn write_finalize(self) -> Result<WriteResult> {
1448        self.finish()
1449    }
1450
1451    /// Finalizes the image to one supplied writer.
1452    ///
1453    /// # Errors
1454    ///
1455    /// Returns an error if the image requires multiple output segments, if
1456    /// secondary output is configured, or if output preparation/writing fails.
1457    pub fn finish_to_writer<W: Write>(self, writer: &mut W) -> Result<WriteResult> {
1458        let path = self.path.clone();
1459        self.finish_to_segment_writers([(path, writer)])
1460    }
1461
1462    /// Finalizes the image to explicitly supplied segment writers.
1463    ///
1464    /// The number of supplied writers must match the number of output segments
1465    /// implied by the current writer configuration.
1466    ///
1467    /// # Errors
1468    ///
1469    /// Returns an error if secondary output is configured, if the writer count
1470    /// does not match the computed segment count, or if output
1471    /// preparation/writing fails.
1472    pub fn finish_to_segment_writers<P, W, I>(self, segment_writers: I) -> Result<WriteResult>
1473    where
1474        P: Into<PathBuf>,
1475        W: Write,
1476        I: IntoIterator<Item = (P, W)>,
1477    {
1478        let PreparedWrite {
1479            path: _,
1480            options,
1481            chunks,
1482            mut spool,
1483            logical_size,
1484            chunk_size,
1485            chunk_count,
1486            chunk_count_u32,
1487            sector_count,
1488        } = self.prepare_write()?;
1489        if options.secondary_segment_filename.is_some() {
1490            return Err(EwfError::Unsupported(
1491                "secondary segment output requires file-backed finish".into(),
1492            ));
1493        }
1494
1495        let segment_writers: Vec<_> = segment_writers
1496            .into_iter()
1497            .map(|(path, writer)| (path.into(), writer))
1498            .collect();
1499        let groups = segment_groups(
1500            chunks,
1501            options.maximum_segment_size,
1502            &options,
1503            sector_count,
1504            u64::from(chunk_count_u32),
1505        )?;
1506        if segment_writers.len() != groups.len() {
1507            return Err(EwfError::Unsupported(
1508                "finish_to_segment_writers requires exactly one writer per output segment".into(),
1509            ));
1510        }
1511        let group_count = groups.len();
1512        let mut segment_paths = Vec::with_capacity(group_count);
1513
1514        if is_ewf2_format(options.format) {
1515            let mut first_chunk = 0_u64;
1516            for (index, ((segment_path, mut writer), group)) in
1517                segment_writers.into_iter().zip(groups).enumerate()
1518            {
1519                let segment_number = u32::try_from(index + 1).map_err(|_| {
1520                    EwfError::Unsupported("EWF2 writer segment count exceeds u32".into())
1521                })?;
1522                let terminal_section_type = if index + 1 == group_count {
1523                    EWF2_DONE_SECTION
1524                } else {
1525                    EWF2_NEXT_SECTION
1526                };
1527                write_ewf2_segment(
1528                    &mut writer,
1529                    &mut spool,
1530                    &group,
1531                    &options,
1532                    Ewf2SegmentWriteContext {
1533                        segment_number,
1534                        first_chunk,
1535                        total_chunk_count: chunk_count_u32,
1536                        sector_count,
1537                        terminal_section_type,
1538                    },
1539                )?;
1540                writer.flush()?;
1541                first_chunk = first_chunk
1542                    .checked_add(u64::try_from(group.len()).expect("usize fits u64"))
1543                    .ok_or_else(|| {
1544                        EwfError::Malformed("writer EWF2 first chunk overflow".into())
1545                    })?;
1546                segment_paths.push(segment_path);
1547            }
1548        } else {
1549            for (index, ((segment_path, mut writer), group)) in
1550                segment_writers.into_iter().zip(groups).enumerate()
1551            {
1552                let segment_number = u16::try_from(index + 1).map_err(|_| {
1553                    EwfError::Unsupported("EWF1 writer segment count exceeds u16".into())
1554                })?;
1555                let sections =
1556                    Ewf1SegmentSections::for_segment(index == 0, index + 1 == group_count);
1557                write_ewf1_segment(
1558                    &mut writer,
1559                    &mut spool,
1560                    &group,
1561                    &options,
1562                    Ewf1SegmentWriteContext {
1563                        segment_number,
1564                        chunk_count: chunk_count_u32,
1565                        sector_count,
1566                        sections,
1567                        terminal_section: TerminalSection::Done,
1568                    },
1569                )?;
1570                writer.flush()?;
1571                segment_paths.push(segment_path);
1572            }
1573        }
1574
1575        Ok(WriteResult {
1576            segment_paths,
1577            secondary_segment_paths: Vec::new(),
1578            logical_size,
1579            chunk_size,
1580            chunk_count,
1581        })
1582    }
1583
1584    fn write_to_current(&mut self, data: &[u8]) -> Result<usize> {
1585        self.ensure_not_aborted()?;
1586        let data_len = u64::try_from(data.len())
1587            .map_err(|_| EwfError::Malformed("writer data length does not fit u64".into()))?;
1588        let end_offset = self
1589            .current_offset
1590            .checked_add(data_len)
1591            .ok_or_else(|| EwfError::Malformed("writer logical input size overflow".into()))?;
1592        if self
1593            .options
1594            .media_size
1595            .is_some_and(|media_size| end_offset > media_size)
1596        {
1597            return Err(EwfError::Unsupported(
1598                "writer write exceeds configured media size".into(),
1599            ));
1600        }
1601        self.forget_encoded_chunks_in_range(self.current_offset, end_offset);
1602        self.raw.write_at(self.current_offset, data)?;
1603        self.current_offset = end_offset;
1604        self.logical_input_size = self.logical_input_size.max(end_offset);
1605        Ok(data.len())
1606    }
1607
1608    fn prepare_write(self) -> Result<PreparedWrite> {
1609        self.ensure_not_aborted()?;
1610        let logical_size = self.logical_size()?;
1611        let sector_count = logical_size / u64::from(self.options.bytes_per_sector);
1612
1613        let EwfWriter {
1614            path,
1615            mut options,
1616            chunk_size,
1617            chunk_capacity,
1618            mut raw,
1619            encoded_chunks,
1620            ..
1621        } = self;
1622
1623        let EncodedSpool {
1624            chunks,
1625            spool,
1626            computed_md5,
1627            computed_sha1,
1628        } = encode_raw_spool(
1629            &mut raw,
1630            encoded_chunks,
1631            logical_size,
1632            chunk_capacity,
1633            chunk_size,
1634            &options,
1635        )?;
1636
1637        options.hashes = effective_write_hashes(&options.hashes, computed_md5, computed_sha1);
1638        validate_session_ranges("sessions", &options.sessions, sector_count)?;
1639        validate_session_ranges("tracks", &options.tracks, sector_count)?;
1640        let chunk_count = u64::try_from(chunks.len())
1641            .map_err(|_| EwfError::Malformed("writer chunk count does not fit u64".into()))?;
1642        let chunk_count_u32 = u32::try_from(chunk_count)
1643            .map_err(|_| EwfError::Unsupported("writer chunk count exceeds u32".into()))?;
1644
1645        Ok(PreparedWrite {
1646            path,
1647            options,
1648            chunks,
1649            spool,
1650            logical_size,
1651            chunk_size,
1652            chunk_count,
1653            chunk_count_u32,
1654            sector_count,
1655        })
1656    }
1657
1658    fn ensure_not_aborted(&self) -> Result<()> {
1659        if self.abort_signaled.load(Ordering::Relaxed) {
1660            return Err(EwfError::Aborted);
1661        }
1662        Ok(())
1663    }
1664
1665    fn ensure_configuration_mutable(&self, label: &str) -> Result<()> {
1666        if self.logical_input_size == 0 {
1667            return Ok(());
1668        }
1669        Err(EwfError::Unsupported(format!(
1670            "writer {label} cannot be changed after media data has been written"
1671        )))
1672    }
1673
1674    fn remember_encoded_data_chunk_at(
1675        &mut self,
1676        chunk: &EncodedDataChunk,
1677        offset: u64,
1678        decoded_len: usize,
1679    ) {
1680        let Some(encoded) = remembered_encoded_data_chunk(
1681            chunk,
1682            &self.options,
1683            self.chunk_size,
1684            offset,
1685            decoded_len,
1686        ) else {
1687            return;
1688        };
1689        self.encoded_chunks
1690            .insert(offset / self.chunk_size, encoded);
1691    }
1692
1693    fn forget_encoded_chunks_in_range(&mut self, start_offset: u64, end_offset: u64) {
1694        if start_offset >= end_offset || self.encoded_chunks.is_empty() {
1695            return;
1696        }
1697
1698        let first_chunk = start_offset / self.chunk_size;
1699        let last_chunk = (end_offset - 1) / self.chunk_size;
1700        let keys: Vec<_> = self
1701            .encoded_chunks
1702            .range(first_chunk..=last_chunk)
1703            .map(|(chunk_index, _)| *chunk_index)
1704            .collect();
1705        for chunk_index in keys {
1706            self.encoded_chunks.remove(&chunk_index);
1707        }
1708    }
1709}
1710
1711fn target_media_input_size(written_size: u64, configured_media_size: Option<u64>) -> Result<u64> {
1712    match configured_media_size {
1713        Some(media_size) if written_size > media_size => Err(EwfError::Unsupported(
1714            "writer configured media size is smaller than written data".into(),
1715        )),
1716        Some(media_size) => Ok(media_size),
1717        None => Ok(written_size),
1718    }
1719}
1720
1721fn padded_logical_size(logical_input_size: u64, bytes_per_sector: u32) -> Result<u64> {
1722    let bytes_per_sector = u64::from(bytes_per_sector);
1723    let sector_count = logical_input_size.div_ceil(bytes_per_sector);
1724    sector_count
1725        .checked_mul(bytes_per_sector)
1726        .ok_or_else(|| EwfError::Malformed("writer logical size overflow".into()))
1727}
1728
1729fn required_media_u32(value: Option<u64>, label: &str) -> Result<u32> {
1730    let value = value.ok_or_else(|| {
1731        EwfError::Unsupported(format!("cannot copy media values without {label}"))
1732    })?;
1733    u32::try_from(value)
1734        .map_err(|_| EwfError::Unsupported(format!("copied {label} does not fit u32")))
1735}
1736
1737fn write_format_from_image_info(info: &ImageInfo) -> WriteFormat {
1738    match info.format {
1739        Format::Ewf2 if image_info_is_physical(info) => WriteFormat::Ewf2Physical,
1740        Format::Ewf2 => WriteFormat::Ewf2Logical,
1741        Format::Ewf1 if info.format_profile == FormatProfile::Smart => WriteFormat::Ewf1Smart,
1742        Format::Ewf1 if image_info_is_physical(info) => WriteFormat::Ewf1Physical,
1743        Format::Ewf1 => WriteFormat::Ewf1Logical,
1744    }
1745}
1746
1747fn image_info_is_physical(info: &ImageInfo) -> bool {
1748    !matches!(info.media.media_type, Some(MediaType::SingleFiles))
1749        && info.media.media_flags.physical
1750}
1751
1752fn write_compression_from_media_method(method: CompressionMethod) -> Result<WriteCompression> {
1753    match method {
1754        CompressionMethod::None => Ok(WriteCompression::None),
1755        CompressionMethod::Zlib => Ok(WriteCompression::Zlib),
1756        CompressionMethod::Bzip2 => Ok(WriteCompression::Bzip2),
1757        CompressionMethod::Unknown(value) => Err(EwfError::Unsupported(format!(
1758            "cannot copy unknown compression method {value}"
1759        ))),
1760    }
1761}
1762
1763fn write_compression_values_from_media_values(
1764    values: CompressionValues,
1765) -> Result<WriteCompressionValues> {
1766    if values.flags.pattern_fill {
1767        return Err(EwfError::Unsupported(
1768            "cannot copy pattern fill compression flag".into(),
1769        ));
1770    }
1771    if values.flags.unknown_bits != 0 {
1772        return Err(EwfError::Unsupported(format!(
1773            "cannot copy unknown compression flags 0x{:02x}",
1774            values.flags.unknown_bits
1775        )));
1776    }
1777
1778    Ok(WriteCompressionValues {
1779        level: write_compression_level_from_media_level(values.level)?,
1780        empty_block: values.flags.empty_block,
1781    })
1782}
1783
1784fn write_compression_level_from_media_level(
1785    level: CompressionLevel,
1786) -> Result<WriteCompressionLevel> {
1787    match level {
1788        CompressionLevel::Default => Ok(WriteCompressionLevel::Default),
1789        CompressionLevel::None => Ok(WriteCompressionLevel::None),
1790        CompressionLevel::Fast => Ok(WriteCompressionLevel::Fast),
1791        CompressionLevel::Best => Ok(WriteCompressionLevel::Best),
1792        CompressionLevel::Unknown(value) => Err(EwfError::Unsupported(format!(
1793            "cannot copy unknown compression level {value}"
1794        ))),
1795    }
1796}
1797
1798fn rewrite_options_from_image_info(info: &ImageInfo) -> Result<WriteOptions> {
1799    let mut options = WriteOptions::default();
1800    options.copy_media_values_from_info(info)?;
1801    options.copy_header_values_from_info(info);
1802    options.header_codepage = info.header_codepage;
1803    options.header_values_date_format = info.header_values_date_format;
1804    options
1805        .acquisition_errors
1806        .clone_from(&info.acquisition_errors);
1807    options.sessions.clone_from(&info.sessions);
1808    options.tracks.clone_from(&info.tracks);
1809    options.memory_extents.clone_from(&info.memory_extents);
1810    options.single_files.clone_from(&info.single_files);
1811    options.ewf2_single_files_tables = info.ewf2_single_files_tables.clone();
1812    options
1813        .ewf2_increment_data
1814        .clone_from(&info.ewf2_increment_data);
1815    options
1816        .ewf2_final_information
1817        .clone_from(&info.ewf2_final_information);
1818    options
1819        .ewf2_restart_data
1820        .clone_from(&info.ewf2_restart_data);
1821    options
1822        .ewf2_analytical_data
1823        .clone_from(&info.ewf2_analytical_data);
1824    validate_options(&options)?;
1825    Ok(options)
1826}
1827
1828fn copy_image_media_to_writer(image: &Image, writer: &mut EwfWriter) -> Result<()> {
1829    if let Some(chunk_count) = image.number_of_chunks() {
1830        for chunk_index in 0..chunk_count {
1831            let chunk = image.read_encoded_data_chunk(chunk_index)?;
1832            writer.write_encoded_data_chunk(&chunk)?;
1833        }
1834        return Ok(());
1835    }
1836
1837    copy_image_media_bytes_to_writer(image, writer)
1838}
1839
1840fn copy_image_media_bytes_to_writer(image: &Image, writer: &mut EwfWriter) -> Result<()> {
1841    let media_size = image.media_size();
1842    let buffer_size = writer.chunk_capacity.clamp(1, 1024 * 1024);
1843    let mut buffer = vec![0; buffer_size];
1844    let mut offset = 0_u64;
1845
1846    while offset < media_size {
1847        let remaining = media_size - offset;
1848        let take = usize::try_from(remaining.min(buffer.len() as u64))
1849            .expect("source image copy is bounded by buffer length");
1850        let read = image.read_at(&mut buffer[..take], offset)?;
1851        if read != take {
1852            return Err(EwfError::Malformed(format!(
1853                "source image read returned {read} bytes, expected {take}"
1854            )));
1855        }
1856        writer.write_all(&buffer[..take])?;
1857        offset = offset
1858            .checked_add(u64::try_from(take).expect("usize fits u64"))
1859            .ok_or_else(|| EwfError::Malformed("source image copy offset overflow".into()))?;
1860    }
1861
1862    Ok(())
1863}
1864
1865impl std::io::Write for EwfWriter {
1866    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1867        self.write_all(buf).map_err(std::io::Error::other)?;
1868        Ok(buf.len())
1869    }
1870
1871    fn flush(&mut self) -> std::io::Result<()> {
1872        Ok(())
1873    }
1874}
1875
1876impl std::io::Seek for EwfWriter {
1877    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
1878        self.seek_position(pos).map_err(std::io::Error::other)
1879    }
1880
1881    fn stream_position(&mut self) -> std::io::Result<u64> {
1882        Ok(self.current_offset)
1883    }
1884}
1885
1886struct WriteHashState {
1887    md5: Md5,
1888    sha1: Sha1,
1889}
1890
1891impl WriteHashState {
1892    fn new() -> Self {
1893        Self {
1894            md5: Md5::new(),
1895            sha1: Sha1::new(),
1896        }
1897    }
1898
1899    fn update(&mut self, data: &[u8]) {
1900        self.md5.update(data);
1901        self.sha1.update(data);
1902    }
1903
1904    fn finalize(self) -> ([u8; 16], [u8; 20]) {
1905        (self.md5.finalize().into(), self.sha1.finalize().into())
1906    }
1907}
1908
1909fn validate_options(options: &WriteOptions) -> Result<()> {
1910    if options.sectors_per_chunk == 0 {
1911        return Err(EwfError::Malformed(
1912            "writer sectors_per_chunk is zero".into(),
1913        ));
1914    }
1915    if options.bytes_per_sector == 0 {
1916        return Err(EwfError::Malformed(
1917            "writer bytes_per_sector is zero".into(),
1918        ));
1919    }
1920    if matches!(options.compression, WriteCompression::Bzip2) && !is_ewf2_format(options.format) {
1921        return Err(EwfError::Unsupported(
1922            "BZip2 writer compression is only supported for EWF2".into(),
1923        ));
1924    }
1925    if matches!(options.compression, WriteCompression::Bzip2)
1926        && options.compression_values.level == WriteCompressionLevel::None
1927    {
1928        return Err(EwfError::Unsupported(
1929            "BZip2 writer compression does not support none compression level".into(),
1930        ));
1931    }
1932    if options.single_files.is_some()
1933        && !matches!(
1934            options.format,
1935            WriteFormat::Ewf1Logical | WriteFormat::Ewf2Logical
1936        )
1937    {
1938        return Err(EwfError::Unsupported(
1939            "single files catalog writing is only supported for logical images".into(),
1940        ));
1941    }
1942    if !options.ewf2_single_files_tables.is_empty() && options.format != WriteFormat::Ewf2Logical {
1943        return Err(EwfError::Unsupported(
1944            "single files auxiliary table writing is only supported for EWF2 logical images".into(),
1945        ));
1946    }
1947    if !options.memory_extents.is_empty() && !is_ewf2_format(options.format) {
1948        return Err(EwfError::Unsupported(
1949            "memory extents writing is only supported for EWF2 images".into(),
1950        ));
1951    }
1952    if (!options.ewf2_increment_data.is_empty() || options.ewf2_final_information.is_some())
1953        && !is_ewf2_format(options.format)
1954    {
1955        return Err(EwfError::Unsupported(
1956            "opaque EWF2 section writing is only supported for EWF2 images".into(),
1957        ));
1958    }
1959    if (options.ewf2_restart_data.is_some() || options.ewf2_analytical_data.is_some())
1960        && !is_ewf2_format(options.format)
1961    {
1962        return Err(EwfError::Unsupported(
1963            "EWF2 application data writing is only supported for EWF2 images".into(),
1964        ));
1965    }
1966    Ok(())
1967}
1968
1969fn normalize_maximum_segment_size(maximum_segment_size: Option<u64>) -> Option<u64> {
1970    maximum_segment_size.filter(|size| *size != 0)
1971}
1972
1973fn normalize_media_size(media_size: Option<u64>) -> Option<u64> {
1974    media_size.filter(|size| *size != 0)
1975}
1976
1977fn writer_chunk_geometry(sectors_per_chunk: u32, bytes_per_sector: u32) -> Result<(u64, usize)> {
1978    if sectors_per_chunk == 0 {
1979        return Err(EwfError::Malformed(
1980            "writer sectors_per_chunk is zero".into(),
1981        ));
1982    }
1983    if bytes_per_sector == 0 {
1984        return Err(EwfError::Malformed(
1985            "writer bytes_per_sector is zero".into(),
1986        ));
1987    }
1988
1989    let chunk_size = u64::from(sectors_per_chunk)
1990        .checked_mul(u64::from(bytes_per_sector))
1991        .ok_or_else(|| EwfError::Malformed("writer chunk size overflow".into()))?;
1992    let chunk_capacity = usize::try_from(chunk_size)
1993        .map_err(|_| EwfError::Unsupported("writer chunk size does not fit usize".into()))?;
1994    Ok((chunk_size, chunk_capacity))
1995}
1996
1997fn validate_write_data_chunk(chunk: &DataChunk) -> Result<()> {
1998    if chunk.corrupted {
1999        return Err(EwfError::Malformed(
2000            "writer cannot write corrupted data chunk".into(),
2001        ));
2002    }
2003    if chunk.data.len() != chunk.logical_size {
2004        return Err(EwfError::Malformed(
2005            "writer data chunk payload length does not match logical size".into(),
2006        ));
2007    }
2008
2009    Ok(())
2010}
2011
2012fn decode_write_encoded_data_chunk(chunk: &EncodedDataChunk) -> Result<Vec<u8>> {
2013    validate_write_encoded_data_chunk(chunk)?;
2014    decode_chunk(
2015        &chunk.data,
2016        encoded_data_chunk_encoding(chunk.encoding),
2017        chunk.logical_size,
2018    )
2019}
2020
2021fn validate_write_encoded_data_chunk(chunk: &EncodedDataChunk) -> Result<()> {
2022    let encoded_size = usize::try_from(chunk.encoded_size)
2023        .map_err(|_| EwfError::Malformed("writer encoded chunk size does not fit usize".into()))?;
2024    if chunk.data.len() != encoded_size {
2025        return Err(EwfError::Malformed(
2026            "writer encoded data chunk payload length does not match encoded size".into(),
2027        ));
2028    }
2029
2030    Ok(())
2031}
2032
2033fn encoded_data_chunk_encoding(encoding: DataChunkEncoding) -> ChunkEncoding {
2034    match encoding {
2035        DataChunkEncoding::Raw => ChunkEncoding::Raw,
2036        DataChunkEncoding::Zlib => ChunkEncoding::Zlib,
2037        DataChunkEncoding::Bzip2 => ChunkEncoding::Bzip2,
2038        DataChunkEncoding::PatternFill(pattern) => ChunkEncoding::PatternFill(pattern),
2039    }
2040}
2041
2042fn remembered_encoded_data_chunk(
2043    chunk: &EncodedDataChunk,
2044    options: &WriteOptions,
2045    target_chunk_size: u64,
2046    offset: u64,
2047    decoded_len: usize,
2048) -> Option<RememberedEncodedChunk> {
2049    if !offset.is_multiple_of(target_chunk_size) || decoded_len != chunk.logical_size {
2050        return None;
2051    }
2052    if u64::try_from(decoded_len).ok()? > target_chunk_size {
2053        return None;
2054    }
2055    if !encoded_data_chunk_encoding_compatible(chunk.encoding, options) {
2056        return None;
2057    }
2058    if validate_encoded_size(
2059        chunk.encoded_size,
2060        target_chunk_size,
2061        encoded_data_chunk_encoding(chunk.encoding),
2062    )
2063    .is_err()
2064    {
2065        return None;
2066    }
2067
2068    let encoded = match chunk.encoding {
2069        DataChunkEncoding::Raw => {
2070            if chunk.has_checksum && !raw_encoded_data_chunk_checksum_is_valid(chunk) {
2071                return None;
2072            }
2073            EncodedChunk {
2074                bytes: chunk.data.clone(),
2075                compressed: false,
2076                has_checksum: chunk.has_checksum,
2077                pattern_fill: None,
2078            }
2079        }
2080        DataChunkEncoding::Zlib | DataChunkEncoding::Bzip2 => EncodedChunk {
2081            bytes: chunk.data.clone(),
2082            compressed: true,
2083            has_checksum: false,
2084            pattern_fill: None,
2085        },
2086        DataChunkEncoding::PatternFill(pattern) => EncodedChunk {
2087            bytes: Vec::new(),
2088            compressed: true,
2089            has_checksum: false,
2090            pattern_fill: Some(pattern),
2091        },
2092    };
2093
2094    Some(RememberedEncodedChunk {
2095        logical_size: decoded_len,
2096        encoding: chunk.encoding,
2097        encoded,
2098    })
2099}
2100
2101fn encoded_data_chunk_encoding_compatible(
2102    encoding: DataChunkEncoding,
2103    options: &WriteOptions,
2104) -> bool {
2105    match encoding {
2106        DataChunkEncoding::Raw => true,
2107        DataChunkEncoding::Zlib => options.compression != WriteCompression::Bzip2,
2108        DataChunkEncoding::Bzip2 => {
2109            is_ewf2_format(options.format) && options.compression == WriteCompression::Bzip2
2110        }
2111        DataChunkEncoding::PatternFill(_) => is_ewf2_format(options.format),
2112    }
2113}
2114
2115fn raw_encoded_data_chunk_checksum_is_valid(chunk: &EncodedDataChunk) -> bool {
2116    let Some(checksum_offset) = chunk.logical_size.checked_add(4) else {
2117        return false;
2118    };
2119    if chunk.data.len() != checksum_offset {
2120        return false;
2121    }
2122    let expected = adler32(&chunk.data[..chunk.logical_size]).to_le_bytes();
2123    chunk.data[chunk.logical_size..] == expected
2124}
2125
2126fn validate_session_ranges(
2127    label: &str,
2128    ranges: &[SectorRange],
2129    media_sector_count: u64,
2130) -> Result<()> {
2131    validate_sector_ranges(label, ranges, media_sector_count)?;
2132    if ranges.is_empty() {
2133        return Ok(());
2134    }
2135
2136    let mut expected_start = 0_u64;
2137    for range in ranges {
2138        if range.first_sector != expected_start {
2139            return Err(EwfError::Unsupported(format!(
2140                "writer {label} must cover the media contiguously from sector 0"
2141            )));
2142        }
2143        expected_start = range
2144            .first_sector
2145            .checked_add(range.sector_count)
2146            .ok_or_else(|| EwfError::Malformed(format!("writer {label} range overflow")))?;
2147    }
2148    if expected_start != media_sector_count {
2149        return Err(EwfError::Unsupported(format!(
2150            "writer {label} must end at the media sector count"
2151        )));
2152    }
2153
2154    Ok(())
2155}
2156
2157fn validate_sector_ranges(
2158    label: &str,
2159    ranges: &[SectorRange],
2160    media_sector_count: u64,
2161) -> Result<()> {
2162    let mut previous_end = 0_u64;
2163    for range in ranges {
2164        if range.sector_count == 0 {
2165            return Err(EwfError::Unsupported(format!(
2166                "writer {label} cannot contain zero-length ranges"
2167            )));
2168        }
2169        if range.first_sector < previous_end {
2170            return Err(EwfError::Unsupported(format!(
2171                "writer {label} ranges must be sorted and non-overlapping"
2172            )));
2173        }
2174        let end = range
2175            .first_sector
2176            .checked_add(range.sector_count)
2177            .ok_or_else(|| EwfError::Malformed(format!("writer {label} range overflow")))?;
2178        if end > media_sector_count {
2179            return Err(EwfError::Unsupported(format!(
2180                "writer {label} range exceeds the media sector count"
2181            )));
2182        }
2183        previous_end = end;
2184    }
2185
2186    Ok(())
2187}
2188
2189fn validate_signed_sector_range_value(label: &str, field: &str, value: u64) -> Result<()> {
2190    if value > SIGNED_SECTOR_RANGE_MAX {
2191        return Err(EwfError::Unsupported(format!(
2192            "writer {label} {field} exceeds signed 64-bit range"
2193        )));
2194    }
2195    Ok(())
2196}
2197
2198fn write_ewf1_segment<W: Write>(
2199    writer: &mut W,
2200    spool: &mut ChunkSpool,
2201    chunks: &[ChunkDescriptor],
2202    options: &WriteOptions,
2203    context: Ewf1SegmentWriteContext,
2204) -> Result<()> {
2205    let local_chunk_count = u32::try_from(chunks.len())
2206        .map_err(|_| EwfError::Unsupported("EWF1 writer segment chunk count exceeds u32".into()))?;
2207    let header = if context.sections.contains(Ewf1SegmentSections::HEADER) {
2208        header_payload(
2209            &options.metadata,
2210            options.header_codepage,
2211            options.compression_values.level,
2212        )?
2213    } else {
2214        None
2215    };
2216    let header2 = if context.sections.contains(Ewf1SegmentSections::HEADER) {
2217        header2_payload(&options.metadata, options.compression_values.level)?
2218    } else {
2219        None
2220    };
2221    let xheader = if context.sections.contains(Ewf1SegmentSections::HEADER) {
2222        xheader_payload(&options.metadata, options.compression_values.level)?
2223    } else {
2224        None
2225    };
2226    let digest = context
2227        .sections
2228        .contains(Ewf1SegmentSections::DIGEST)
2229        .then(|| digest_payload(&options.hashes))
2230        .flatten();
2231    let xhash = if context.sections.contains(Ewf1SegmentSections::DIGEST) {
2232        xhash_payload(&options.hashes, options.compression_values.level)?
2233    } else {
2234        None
2235    };
2236    let error2 = if context.sections.contains(Ewf1SegmentSections::ERRORS) {
2237        ewf1_error2_payload(&options.acquisition_errors)?
2238    } else {
2239        None
2240    };
2241    let session = if context.sections.contains(Ewf1SegmentSections::SESSIONS) {
2242        ewf1_session_payload(&options.sessions, &options.tracks)?
2243    } else {
2244        None
2245    };
2246    let ltree = if context.sections.contains(Ewf1SegmentSections::DIGEST)
2247        && options.format == WriteFormat::Ewf1Logical
2248    {
2249        options
2250            .single_files
2251            .as_ref()
2252            .map(ewf1_ltree_payload)
2253            .transpose()?
2254    } else {
2255        None
2256    };
2257    let chunk_payload_size = chunks.iter().try_fold(0_u64, |total, chunk| {
2258        total
2259            .checked_add(chunk.data_size)
2260            .ok_or_else(|| EwfError::Malformed("writer chunk payload size overflow".into()))
2261    })?;
2262
2263    let header_desc_offset = ewf1::FILE_HEADER_SIZE as u64;
2264    let volume_data_size = volume_data_size(options.format);
2265    let header2_desc_offset = if let Some(header) = &header {
2266        header_desc_offset
2267            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2268            .and_then(|value| value.checked_add(u64::try_from(header.len()).ok()?))
2269            .ok_or_else(|| EwfError::Malformed("writer volume descriptor offset overflow".into()))?
2270    } else {
2271        header_desc_offset
2272    };
2273    let xheader_desc_offset = if let Some(header2) = &header2 {
2274        header2_desc_offset
2275            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2276            .and_then(|value| value.checked_add(u64::try_from(header2.len()).ok()?))
2277            .ok_or_else(|| EwfError::Malformed("writer volume descriptor offset overflow".into()))?
2278    } else {
2279        header2_desc_offset
2280    };
2281    let volume_desc_offset = if let Some(xheader) = &xheader {
2282        xheader_desc_offset
2283            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2284            .and_then(|value| value.checked_add(u64::try_from(xheader.len()).ok()?))
2285            .ok_or_else(|| EwfError::Malformed("writer volume descriptor offset overflow".into()))?
2286    } else {
2287        xheader_desc_offset
2288    };
2289    let volume_data_offset = volume_desc_offset + ewf1::SECTION_DESCRIPTOR_SIZE as u64;
2290    let session_desc_offset = volume_data_offset + volume_data_size;
2291    let sectors_desc_offset = if let Some(session) = &session {
2292        session_desc_offset
2293            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2294            .and_then(|value| value.checked_add(u64::try_from(session.len()).ok()?))
2295            .ok_or_else(|| {
2296                EwfError::Malformed("writer sectors descriptor offset overflow".into())
2297            })?
2298    } else {
2299        session_desc_offset
2300    };
2301    let sectors_data_offset = sectors_desc_offset + ewf1::SECTION_DESCRIPTOR_SIZE as u64;
2302    let table_desc_offset = sectors_data_offset
2303        .checked_add(chunk_payload_size)
2304        .ok_or_else(|| EwfError::Malformed("writer table descriptor offset overflow".into()))?;
2305    let table_data_offset = table_desc_offset + ewf1::SECTION_DESCRIPTOR_SIZE as u64;
2306    let table_entry_bytes = u64::from(local_chunk_count)
2307        .checked_mul(4)
2308        .ok_or_else(|| EwfError::Malformed("writer table entry size overflow".into()))?;
2309    let table_footer_bytes = if matches!(options.format, WriteFormat::Ewf1Smart) {
2310        0
2311    } else {
2312        4
2313    };
2314    let table_data_size = 24_u64
2315        .checked_add(table_entry_bytes)
2316        .and_then(|value| value.checked_add(table_footer_bytes))
2317        .ok_or_else(|| EwfError::Malformed("writer table data size overflow".into()))?;
2318    let table2_desc_offset = table_data_offset
2319        .checked_add(table_data_size)
2320        .ok_or_else(|| EwfError::Malformed("writer table2 descriptor offset overflow".into()))?;
2321    let writes_table2 = matches!(
2322        options.format,
2323        WriteFormat::Ewf1Physical | WriteFormat::Ewf1Logical
2324    );
2325    let post_table_desc_offset = if writes_table2 {
2326        table2_desc_offset
2327            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2328            .and_then(|value| value.checked_add(table_data_size))
2329    } else {
2330        table_data_offset.checked_add(table_data_size)
2331    }
2332    .ok_or_else(|| EwfError::Malformed("writer post-table descriptor offset overflow".into()))?;
2333    let ltree_desc_offset = post_table_desc_offset;
2334    let error2_desc_offset = if let Some(ltree) = &ltree {
2335        ltree_desc_offset
2336            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2337            .and_then(|value| value.checked_add(u64::try_from(ltree.len()).ok()?))
2338            .ok_or_else(|| EwfError::Malformed("writer error2 descriptor offset overflow".into()))?
2339    } else {
2340        post_table_desc_offset
2341    };
2342    let digest_desc_offset = if let Some(error2) = &error2 {
2343        error2_desc_offset
2344            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2345            .and_then(|value| value.checked_add(u64::try_from(error2.len()).ok()?))
2346            .ok_or_else(|| EwfError::Malformed("writer digest descriptor offset overflow".into()))?
2347    } else {
2348        error2_desc_offset
2349    };
2350    let xhash_desc_offset = if let Some(digest) = &digest {
2351        digest_desc_offset
2352            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2353            .and_then(|value| value.checked_add(u64::try_from(digest.len()).ok()?))
2354            .ok_or_else(|| EwfError::Malformed("writer xhash descriptor offset overflow".into()))?
2355    } else {
2356        digest_desc_offset
2357    };
2358    let done_desc_offset = if let Some(xhash) = &xhash {
2359        xhash_desc_offset
2360            .checked_add(ewf1::SECTION_DESCRIPTOR_SIZE as u64)
2361            .and_then(|value| value.checked_add(u64::try_from(xhash.len()).ok()?))
2362            .ok_or_else(|| EwfError::Malformed("writer done descriptor offset overflow".into()))?
2363    } else {
2364        xhash_desc_offset
2365    };
2366    let after_tables_desc_offset = if ltree.is_some() {
2367        ltree_desc_offset
2368    } else if error2.is_some() {
2369        error2_desc_offset
2370    } else if digest.is_some() {
2371        digest_desc_offset
2372    } else if xhash.is_some() {
2373        xhash_desc_offset
2374    } else {
2375        done_desc_offset
2376    };
2377    let after_ltree_desc_offset = if error2.is_some() {
2378        error2_desc_offset
2379    } else if digest.is_some() {
2380        digest_desc_offset
2381    } else if xhash.is_some() {
2382        xhash_desc_offset
2383    } else {
2384        done_desc_offset
2385    };
2386    let after_error2_desc_offset = if digest.is_some() {
2387        digest_desc_offset
2388    } else if xhash.is_some() {
2389        xhash_desc_offset
2390    } else {
2391        done_desc_offset
2392    };
2393    let after_digest_desc_offset = if xhash.is_some() {
2394        xhash_desc_offset
2395    } else {
2396        done_desc_offset
2397    };
2398    let post_sectors_desc_offset = table_desc_offset;
2399
2400    writer.write_all(ewf1_signature(options.format))?;
2401    writer.write_all(&[1])?;
2402    writer.write_all(&context.segment_number.to_le_bytes())?;
2403    writer.write_all(&0_u16.to_le_bytes())?;
2404
2405    if let Some(header) = &header {
2406        writer.write_all(&section_desc(
2407            b"header",
2408            if header2.is_some() {
2409                header2_desc_offset
2410            } else if xheader.is_some() {
2411                xheader_desc_offset
2412            } else {
2413                volume_desc_offset
2414            },
2415            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2416                + u64::try_from(header.len()).expect("usize fits u64"),
2417        ))?;
2418        writer.write_all(header)?;
2419    }
2420
2421    if let Some(header2) = &header2 {
2422        writer.write_all(&section_desc(
2423            b"header2",
2424            if xheader.is_some() {
2425                xheader_desc_offset
2426            } else {
2427                volume_desc_offset
2428            },
2429            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2430                + u64::try_from(header2.len()).expect("usize fits u64"),
2431        ))?;
2432        writer.write_all(header2)?;
2433    }
2434
2435    if let Some(xheader) = &xheader {
2436        writer.write_all(&section_desc(
2437            b"xheader",
2438            volume_desc_offset,
2439            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2440                + u64::try_from(xheader.len()).expect("usize fits u64"),
2441        ))?;
2442        writer.write_all(xheader)?;
2443    }
2444
2445    writer.write_all(&section_desc(
2446        b"volume",
2447        if session.is_some() {
2448            session_desc_offset
2449        } else {
2450            sectors_desc_offset
2451        },
2452        ewf1::SECTION_DESCRIPTOR_SIZE as u64 + volume_data_size,
2453    ))?;
2454    writer.write_all(&volume_data(
2455        options,
2456        context.chunk_count,
2457        context.sector_count,
2458    )?)?;
2459
2460    if let Some(session) = session {
2461        writer.write_all(&section_desc(
2462            b"session",
2463            sectors_desc_offset,
2464            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2465                + u64::try_from(session.len()).expect("usize fits u64"),
2466        ))?;
2467        writer.write_all(&session)?;
2468    }
2469
2470    writer.write_all(&section_desc(
2471        b"sectors",
2472        post_sectors_desc_offset,
2473        ewf1::SECTION_DESCRIPTOR_SIZE as u64 + chunk_payload_size,
2474    ))?;
2475    for chunk in chunks {
2476        spool.copy_chunk_to(chunk, writer)?;
2477    }
2478
2479    writer.write_all(&section_desc(
2480        b"table",
2481        if writes_table2 {
2482            table2_desc_offset
2483        } else {
2484            after_tables_desc_offset
2485        },
2486        ewf1::SECTION_DESCRIPTOR_SIZE as u64 + table_data_size,
2487    ))?;
2488    let table_capacity = usize::try_from(table_data_size)
2489        .map_err(|_| EwfError::Malformed("writer table data size does not fit usize".into()))?;
2490    let mut table_data = Vec::with_capacity(table_capacity);
2491    append_ewf1_table_data(
2492        &mut table_data,
2493        local_chunk_count,
2494        sectors_data_offset,
2495        chunks,
2496        table_footer_bytes != 0,
2497    )?;
2498    writer.write_all(&table_data)?;
2499
2500    if writes_table2 {
2501        writer.write_all(&section_desc(
2502            b"table2",
2503            after_tables_desc_offset,
2504            ewf1::SECTION_DESCRIPTOR_SIZE as u64 + table_data_size,
2505        ))?;
2506        let mut table2_data = Vec::with_capacity(table_capacity);
2507        append_ewf1_table_data(
2508            &mut table2_data,
2509            local_chunk_count,
2510            sectors_data_offset,
2511            chunks,
2512            true,
2513        )?;
2514        writer.write_all(&table2_data)?;
2515    }
2516
2517    if let Some(ltree) = &ltree {
2518        writer.write_all(&section_desc(
2519            b"ltree",
2520            after_ltree_desc_offset,
2521            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2522                + u64::try_from(ltree.len()).expect("usize fits u64"),
2523        ))?;
2524        writer.write_all(ltree)?;
2525    }
2526
2527    if let Some(error2) = error2 {
2528        writer.write_all(&section_desc(
2529            b"error2",
2530            after_error2_desc_offset,
2531            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2532                + u64::try_from(error2.len()).expect("usize fits u64"),
2533        ))?;
2534        writer.write_all(&error2)?;
2535    }
2536    if let Some(digest) = digest {
2537        writer.write_all(&section_desc(
2538            b"digest",
2539            after_digest_desc_offset,
2540            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2541                + u64::try_from(digest.len()).expect("usize fits u64"),
2542        ))?;
2543        writer.write_all(&digest)?;
2544    }
2545    if let Some(xhash) = xhash {
2546        writer.write_all(&section_desc(
2547            b"xhash",
2548            done_desc_offset,
2549            ewf1::SECTION_DESCRIPTOR_SIZE as u64
2550                + u64::try_from(xhash.len()).expect("usize fits u64"),
2551        ))?;
2552        writer.write_all(&xhash)?;
2553    }
2554    writer.write_all(&section_desc(
2555        context.terminal_section.ewf1_type(),
2556        done_desc_offset,
2557        ewf1::SECTION_DESCRIPTOR_SIZE as u64,
2558    ))?;
2559
2560    Ok(())
2561}
2562
2563#[derive(Debug, Clone, Copy)]
2564struct Ewf1SegmentWriteContext {
2565    segment_number: u16,
2566    chunk_count: u32,
2567    sector_count: u64,
2568    sections: Ewf1SegmentSections,
2569    terminal_section: TerminalSection,
2570}
2571
2572#[derive(Debug, Clone, Copy)]
2573struct Ewf1SegmentSections {
2574    bits: u8,
2575}
2576
2577impl Ewf1SegmentSections {
2578    const HEADER: u8 = 1 << 0;
2579    const DIGEST: u8 = 1 << 1;
2580    const ERRORS: u8 = 1 << 2;
2581    const SESSIONS: u8 = 1 << 3;
2582
2583    fn for_segment(is_first: bool, is_last: bool) -> Self {
2584        let mut bits = 0;
2585        if is_first {
2586            bits |= Self::HEADER | Self::ERRORS | Self::SESSIONS;
2587        }
2588        if is_last {
2589            bits |= Self::DIGEST;
2590        }
2591        Self { bits }
2592    }
2593
2594    fn contains(self, flag: u8) -> bool {
2595        self.bits & flag != 0
2596    }
2597}
2598
2599fn ewf1_signature(format: WriteFormat) -> &'static [u8; 8] {
2600    match format {
2601        WriteFormat::Ewf1Physical | WriteFormat::Ewf1Smart => &ewf1::EVF_SIGNATURE,
2602        WriteFormat::Ewf1Logical => &ewf1::LVF_SIGNATURE,
2603        WriteFormat::Ewf2Physical | WriteFormat::Ewf2Logical => {
2604            unreachable!("EWF2 formats are not EWF1")
2605        }
2606    }
2607}
2608
2609fn is_ewf2_format(format: WriteFormat) -> bool {
2610    matches!(format, WriteFormat::Ewf2Physical | WriteFormat::Ewf2Logical)
2611}
2612
2613fn write_format_is_physical(format: WriteFormat) -> bool {
2614    matches!(
2615        format,
2616        WriteFormat::Ewf1Physical | WriteFormat::Ewf1Smart | WriteFormat::Ewf2Physical
2617    )
2618}
2619
2620fn writer_media_flags(format: WriteFormat, profile: WriteMediaProfile) -> MediaFlags {
2621    MediaFlags {
2622        physical: write_format_is_physical(format),
2623        fastbloc: profile.fastbloc,
2624        tableau: profile.tableau,
2625    }
2626}
2627
2628#[derive(Debug, Clone, Copy)]
2629struct Ewf2SegmentWriteContext {
2630    segment_number: u32,
2631    first_chunk: u64,
2632    total_chunk_count: u32,
2633    sector_count: u64,
2634    terminal_section_type: u32,
2635}
2636
2637#[derive(Debug, Clone, Copy)]
2638struct Ewf2SectionPlan {
2639    data_offset: u64,
2640    previous_desc_offset: u64,
2641}
2642
2643fn plan_ewf2_section(
2644    current_offset: &mut u64,
2645    previous_desc_offset: &mut u64,
2646    data_size: u64,
2647    label: &str,
2648) -> Result<Ewf2SectionPlan> {
2649    let data_offset = *current_offset;
2650    let desc_offset = checked_add(data_offset, data_size, label)?;
2651    let section = Ewf2SectionPlan {
2652        data_offset,
2653        previous_desc_offset: *previous_desc_offset,
2654    };
2655    *current_offset = checked_add(
2656        desc_offset,
2657        ewf2::SECTION_DESCRIPTOR_SIZE as u64,
2658        "EWF2 next section",
2659    )?;
2660    *previous_desc_offset = desc_offset;
2661    Ok(section)
2662}
2663
2664fn write_ewf2_section<W: Write>(
2665    writer: &mut W,
2666    section_type: u32,
2667    payload: &[u8],
2668    section: Ewf2SectionPlan,
2669) -> Result<()> {
2670    writer.write_all(payload)?;
2671    writer.write_all(&ewf2_section_desc(
2672        section_type,
2673        u64::try_from(payload.len()).expect("usize fits u64"),
2674        section.previous_desc_offset,
2675    ))?;
2676    Ok(())
2677}
2678
2679fn write_ewf2_segment<W: Write>(
2680    writer: &mut W,
2681    spool: &mut ChunkSpool,
2682    chunks: &[ChunkDescriptor],
2683    options: &WriteOptions,
2684    context: Ewf2SegmentWriteContext,
2685) -> Result<()> {
2686    let local_chunk_count = u32::try_from(chunks.len())
2687        .map_err(|_| EwfError::Unsupported("EWF2 writer segment chunk count exceeds u32".into()))?;
2688    let device_information_payload = ewf2_device_information_payload(
2689        options,
2690        context.sector_count,
2691        u64::from(context.total_chunk_count),
2692    );
2693    let device_information =
2694        ewf2_device_information_section_payload(&device_information_payload, options)?;
2695    let case_data_payload = ewf2_case_data_payload(options, u64::from(context.total_chunk_count));
2696    let case_data = ewf2_metadata_section_payload(
2697        &case_data_payload,
2698        options.compression,
2699        options.compression_values.level,
2700    )?;
2701    let error_table = if context.segment_number == 1 {
2702        ewf2_error_table_payload(&options.acquisition_errors)?
2703    } else {
2704        None
2705    };
2706    let session_table = if context.segment_number == 1 {
2707        ewf2_session_table_payload(&options.sessions, &options.tracks)?
2708    } else {
2709        None
2710    };
2711    let memory_extents_table = if context.segment_number == 1 {
2712        ewf2_memory_extents_table_payload(&options.memory_extents)
2713    } else {
2714        None
2715    };
2716    let increment_data: &[Vec<u8>] = if context.segment_number == 1 {
2717        &options.ewf2_increment_data
2718    } else {
2719        &[]
2720    };
2721    let single_files_data = if context.segment_number == 1 {
2722        options
2723            .single_files
2724            .as_ref()
2725            .map(ewf2_single_files_data_payload)
2726            .transpose()?
2727    } else {
2728        None
2729    };
2730    let single_files_table_0x21 = if context.segment_number == 1 {
2731        ewf2_single_files_aux_u64_table_payload(
2732            &options.ewf2_single_files_tables.table_0x21_entries,
2733        )
2734    } else {
2735        None
2736    };
2737    let single_files_md5_hash_table = if context.segment_number == 1 {
2738        ewf2_single_files_md5_hash_table_payload(&options.ewf2_single_files_tables.md5_hashes)
2739    } else {
2740        None
2741    };
2742    let single_files_table_0x23 = if context.segment_number == 1 {
2743        ewf2_single_files_aux_u64_table_payload(
2744            &options.ewf2_single_files_tables.table_0x23_entries,
2745        )
2746    } else {
2747        None
2748    };
2749    let writes_hash_sections = context.terminal_section_type == EWF2_DONE_SECTION;
2750    let md5_hash = writes_hash_sections
2751        .then(|| options.hashes.md5.map(ewf2_hash_payload))
2752        .flatten();
2753    let sha1_hash = writes_hash_sections
2754        .then(|| options.hashes.sha1.map(ewf2_hash_payload))
2755        .flatten();
2756    let final_information = if writes_hash_sections {
2757        options.ewf2_final_information.as_deref()
2758    } else {
2759        None
2760    };
2761    let analytical_data = if writes_hash_sections {
2762        options
2763            .ewf2_analytical_data
2764            .as_deref()
2765            .map(|data| {
2766                ewf2_string_section_payload(
2767                    data,
2768                    options.compression,
2769                    options.compression_values.level,
2770                )
2771            })
2772            .transpose()?
2773    } else {
2774        None
2775    };
2776    let restart_data = if writes_hash_sections {
2777        options
2778            .ewf2_restart_data
2779            .as_deref()
2780            .map(|data| {
2781                ewf2_string_section_payload(
2782                    data,
2783                    options.compression,
2784                    options.compression_values.level,
2785                )
2786            })
2787            .transpose()?
2788    } else {
2789        None
2790    };
2791    let table_entry_bytes = u64::from(local_chunk_count)
2792        .checked_mul(ewf2::TABLE_ENTRY_SIZE as u64)
2793        .ok_or_else(|| EwfError::Malformed("writer EWF2 table entry size overflow".into()))?;
2794    let table_data_size = (EWF2_TABLE_HEADER_V2_SIZE as u64)
2795        .checked_add(table_entry_bytes)
2796        .and_then(|value| value.checked_add(EWF2_TABLE_FOOTER_SIZE as u64))
2797        .ok_or_else(|| EwfError::Malformed("writer EWF2 table size overflow".into()))?;
2798    let chunk_payload_size = chunks.iter().try_fold(0_u64, |total, chunk| {
2799        total
2800            .checked_add(chunk.data_size)
2801            .ok_or_else(|| EwfError::Malformed("writer EWF2 chunk payload size overflow".into()))
2802    })?;
2803
2804    let mut current_offset = ewf2::FILE_HEADER_SIZE as u64;
2805    let mut previous_desc_offset = 0_u64;
2806
2807    let device_section = plan_ewf2_section(
2808        &mut current_offset,
2809        &mut previous_desc_offset,
2810        u64::try_from(device_information.len()).expect("usize fits u64"),
2811        "EWF2 device information",
2812    )?;
2813    let case_section = plan_ewf2_section(
2814        &mut current_offset,
2815        &mut previous_desc_offset,
2816        u64::try_from(case_data.len()).expect("usize fits u64"),
2817        "EWF2 case data",
2818    )?;
2819    let error_section = error_table
2820        .as_ref()
2821        .map(|payload| {
2822            plan_ewf2_section(
2823                &mut current_offset,
2824                &mut previous_desc_offset,
2825                u64::try_from(payload.len()).expect("usize fits u64"),
2826                "EWF2 error table",
2827            )
2828        })
2829        .transpose()?;
2830    let session_section = session_table
2831        .as_ref()
2832        .map(|payload| {
2833            plan_ewf2_section(
2834                &mut current_offset,
2835                &mut previous_desc_offset,
2836                u64::try_from(payload.len()).expect("usize fits u64"),
2837                "EWF2 session table",
2838            )
2839        })
2840        .transpose()?;
2841    let memory_extents_section = memory_extents_table
2842        .as_ref()
2843        .map(|payload| {
2844            plan_ewf2_section(
2845                &mut current_offset,
2846                &mut previous_desc_offset,
2847                u64::try_from(payload.len()).expect("usize fits u64"),
2848                "EWF2 memory extents table",
2849            )
2850        })
2851        .transpose()?;
2852
2853    let mut increment_sections = Vec::with_capacity(increment_data.len());
2854    for payload in increment_data {
2855        increment_sections.push(plan_ewf2_section(
2856            &mut current_offset,
2857            &mut previous_desc_offset,
2858            u64::try_from(payload.len()).expect("usize fits u64"),
2859            "EWF2 increment data",
2860        )?);
2861    }
2862
2863    let single_files_section = single_files_data
2864        .as_ref()
2865        .map(|payload| {
2866            plan_ewf2_section(
2867                &mut current_offset,
2868                &mut previous_desc_offset,
2869                u64::try_from(payload.len()).expect("usize fits u64"),
2870                "EWF2 single files data",
2871            )
2872        })
2873        .transpose()?;
2874    let single_files_table_0x21_section = single_files_table_0x21
2875        .as_ref()
2876        .map(|payload| {
2877            plan_ewf2_section(
2878                &mut current_offset,
2879                &mut previous_desc_offset,
2880                u64::try_from(payload.len()).expect("usize fits u64"),
2881                "EWF2 single files 0x21 table",
2882            )
2883        })
2884        .transpose()?;
2885    let single_files_md5_hash_table_section = single_files_md5_hash_table
2886        .as_ref()
2887        .map(|payload| {
2888            plan_ewf2_section(
2889                &mut current_offset,
2890                &mut previous_desc_offset,
2891                u64::try_from(payload.len()).expect("usize fits u64"),
2892                "EWF2 single files MD5 hash table",
2893            )
2894        })
2895        .transpose()?;
2896    let single_files_table_0x23_section = single_files_table_0x23
2897        .as_ref()
2898        .map(|payload| {
2899            plan_ewf2_section(
2900                &mut current_offset,
2901                &mut previous_desc_offset,
2902                u64::try_from(payload.len()).expect("usize fits u64"),
2903                "EWF2 single files 0x23 table",
2904            )
2905        })
2906        .transpose()?;
2907
2908    let sectors_section = plan_ewf2_section(
2909        &mut current_offset,
2910        &mut previous_desc_offset,
2911        chunk_payload_size,
2912        "EWF2 sector data",
2913    )?;
2914    let table_section = plan_ewf2_section(
2915        &mut current_offset,
2916        &mut previous_desc_offset,
2917        table_data_size,
2918        "EWF2 sector table",
2919    )?;
2920    let md5_section = md5_hash
2921        .as_ref()
2922        .map(|payload| {
2923            plan_ewf2_section(
2924                &mut current_offset,
2925                &mut previous_desc_offset,
2926                u64::try_from(payload.len()).expect("usize fits u64"),
2927                "EWF2 MD5 hash",
2928            )
2929        })
2930        .transpose()?;
2931    let sha1_section = sha1_hash
2932        .as_ref()
2933        .map(|payload| {
2934            plan_ewf2_section(
2935                &mut current_offset,
2936                &mut previous_desc_offset,
2937                u64::try_from(payload.len()).expect("usize fits u64"),
2938                "EWF2 SHA1 hash",
2939            )
2940        })
2941        .transpose()?;
2942    let final_information_section = final_information
2943        .map(|payload| {
2944            plan_ewf2_section(
2945                &mut current_offset,
2946                &mut previous_desc_offset,
2947                u64::try_from(payload.len()).expect("usize fits u64"),
2948                "EWF2 final information",
2949            )
2950        })
2951        .transpose()?;
2952    let analytical_section = analytical_data
2953        .as_ref()
2954        .map(|payload| {
2955            plan_ewf2_section(
2956                &mut current_offset,
2957                &mut previous_desc_offset,
2958                u64::try_from(payload.len()).expect("usize fits u64"),
2959                "EWF2 analytical data",
2960            )
2961        })
2962        .transpose()?;
2963    let restart_section = restart_data
2964        .as_ref()
2965        .map(|payload| {
2966            plan_ewf2_section(
2967                &mut current_offset,
2968                &mut previous_desc_offset,
2969                u64::try_from(payload.len()).expect("usize fits u64"),
2970                "EWF2 restart data",
2971            )
2972        })
2973        .transpose()?;
2974    let terminal_section = plan_ewf2_section(
2975        &mut current_offset,
2976        &mut previous_desc_offset,
2977        0,
2978        "EWF2 terminal",
2979    )?;
2980
2981    writer.write_all(ewf2_signature(options.format))?;
2982    writer.write_all(&[2, 1])?;
2983    writer.write_all(&ewf2_compression_method(options.compression).to_le_bytes())?;
2984    writer.write_all(&context.segment_number.to_le_bytes())?;
2985    writer.write_all(&options.set_identifier.unwrap_or([0; 16]))?;
2986
2987    write_ewf2_section(
2988        writer,
2989        EWF2_DEVICE_INFORMATION_SECTION,
2990        &device_information,
2991        device_section,
2992    )?;
2993
2994    write_ewf2_section(writer, EWF2_CASE_DATA_SECTION, &case_data, case_section)?;
2995
2996    if let (Some(error_table), Some(section)) = (&error_table, error_section) {
2997        write_ewf2_section(writer, EWF2_ERROR_TABLE_SECTION, error_table, section)?;
2998    }
2999
3000    if let (Some(session_table), Some(section)) = (&session_table, session_section) {
3001        write_ewf2_section(writer, EWF2_SESSION_TABLE_SECTION, session_table, section)?;
3002    }
3003
3004    if let (Some(memory_extents_table), Some(section)) =
3005        (&memory_extents_table, memory_extents_section)
3006    {
3007        write_ewf2_section(
3008            writer,
3009            EWF2_MEMORY_EXTENTS_TABLE_SECTION,
3010            memory_extents_table,
3011            section,
3012        )?;
3013    }
3014
3015    for (payload, section) in increment_data.iter().zip(&increment_sections) {
3016        write_ewf2_section(writer, EWF2_INCREMENT_DATA_SECTION, payload, *section)?;
3017    }
3018
3019    if let (Some(single_files_data), Some(section)) = (&single_files_data, single_files_section) {
3020        write_ewf2_section(
3021            writer,
3022            EWF2_SINGLE_FILES_DATA_SECTION,
3023            single_files_data,
3024            section,
3025        )?;
3026    }
3027
3028    if let (Some(single_files_table_0x21), Some(section)) =
3029        (&single_files_table_0x21, single_files_table_0x21_section)
3030    {
3031        write_ewf2_section(
3032            writer,
3033            EWF2_SINGLE_FILES_TABLE_SECTION,
3034            single_files_table_0x21,
3035            section,
3036        )?;
3037    }
3038
3039    if let (Some(single_files_md5_hash_table), Some(section)) = (
3040        &single_files_md5_hash_table,
3041        single_files_md5_hash_table_section,
3042    ) {
3043        write_ewf2_section(
3044            writer,
3045            EWF2_SINGLE_FILES_MD5_HASH_TABLE_SECTION,
3046            single_files_md5_hash_table,
3047            section,
3048        )?;
3049    }
3050
3051    if let (Some(single_files_table_0x23), Some(section)) =
3052        (&single_files_table_0x23, single_files_table_0x23_section)
3053    {
3054        write_ewf2_section(
3055            writer,
3056            EWF2_SINGLE_FILES_UNKNOWN_TABLE_SECTION,
3057            single_files_table_0x23,
3058            section,
3059        )?;
3060    }
3061
3062    for chunk in chunks {
3063        spool.copy_chunk_to(chunk, writer)?;
3064    }
3065    writer.write_all(&ewf2_section_desc(
3066        EWF2_SECTOR_DATA_SECTION,
3067        chunk_payload_size,
3068        sectors_section.previous_desc_offset,
3069    ))?;
3070
3071    let table_capacity = usize::try_from(table_data_size)
3072        .map_err(|_| EwfError::Malformed("writer EWF2 table size does not fit usize".into()))?;
3073    let mut table_data = Vec::with_capacity(table_capacity);
3074    table_data.extend_from_slice(&ewf2_table_header(context.first_chunk, local_chunk_count));
3075    let table_entries_start = table_data.len();
3076    let mut chunk_offset = sectors_section.data_offset;
3077    for chunk in chunks {
3078        let chunk_size = u32::try_from(chunk.data_size)
3079            .map_err(|_| EwfError::Unsupported("EWF2 writer chunk data size exceeds u32".into()))?;
3080        table_data.extend_from_slice(&ewf2_table_entry(
3081            chunk.pattern_fill.unwrap_or(chunk_offset),
3082            chunk_size,
3083            chunk.compressed,
3084            chunk.has_checksum,
3085            chunk.pattern_fill.is_some(),
3086        ));
3087        chunk_offset = checked_add(chunk_offset, chunk.data_size, "EWF2 chunk data offset")?;
3088    }
3089    let entries_checksum = adler32(&table_data[table_entries_start..]);
3090    table_data.extend_from_slice(&entries_checksum.to_le_bytes());
3091    table_data.extend_from_slice(&[0; EWF2_TABLE_FOOTER_SIZE - 4]);
3092    write_ewf2_section(
3093        writer,
3094        EWF2_SECTOR_TABLE_SECTION,
3095        &table_data,
3096        table_section,
3097    )?;
3098
3099    if let (Some(md5_hash), Some(section)) = (&md5_hash, md5_section) {
3100        write_ewf2_section(writer, EWF2_MD5_HASH_SECTION, md5_hash, section)?;
3101    }
3102    if let (Some(sha1_hash), Some(section)) = (&sha1_hash, sha1_section) {
3103        write_ewf2_section(writer, EWF2_SHA1_HASH_SECTION, sha1_hash, section)?;
3104    }
3105    if let (Some(final_information), Some(section)) = (final_information, final_information_section)
3106    {
3107        write_ewf2_section(
3108            writer,
3109            EWF2_FINAL_INFORMATION_SECTION,
3110            final_information,
3111            section,
3112        )?;
3113    }
3114    if let (Some(analytical_data), Some(section)) = (&analytical_data, analytical_section) {
3115        write_ewf2_section(
3116            writer,
3117            EWF2_ANALYTICAL_DATA_SECTION,
3118            analytical_data,
3119            section,
3120        )?;
3121    }
3122    if let (Some(restart_data), Some(section)) = (&restart_data, restart_section) {
3123        write_ewf2_section(writer, EWF2_RESTART_DATA_SECTION, restart_data, section)?;
3124    }
3125    writer.write_all(&ewf2_section_desc(
3126        context.terminal_section_type,
3127        0,
3128        terminal_section.previous_desc_offset,
3129    ))?;
3130
3131    Ok(())
3132}
3133
3134fn ewf2_signature(format: WriteFormat) -> &'static [u8; 8] {
3135    match format {
3136        WriteFormat::Ewf2Physical => &ewf2::EX01_SIGNATURE,
3137        WriteFormat::Ewf2Logical => &ewf2::LEF2_SIGNATURE,
3138        WriteFormat::Ewf1Physical | WriteFormat::Ewf1Logical | WriteFormat::Ewf1Smart => {
3139            unreachable!("EWF1 formats are not EWF2")
3140        }
3141    }
3142}
3143
3144fn ewf2_compression_method(compression: WriteCompression) -> u16 {
3145    match compression {
3146        // Common EWF readers reject EWF2 segment headers that use COMPRESSION_NONE.
3147        // Raw chunks are still represented by table flags; the header method must
3148        // name a supported compression family for the segment.
3149        WriteCompression::None | WriteCompression::Zlib => 1,
3150        WriteCompression::Bzip2 => 2,
3151    }
3152}
3153
3154fn zlib_compression(level: WriteCompressionLevel) -> ZlibCompression {
3155    match level {
3156        WriteCompressionLevel::Default => ZlibCompression::default(),
3157        WriteCompressionLevel::None => ZlibCompression::none(),
3158        WriteCompressionLevel::Fast => ZlibCompression::fast(),
3159        WriteCompressionLevel::Best => ZlibCompression::best(),
3160    }
3161}
3162
3163fn empty_block_zlib_compression(level: WriteCompressionLevel) -> ZlibCompression {
3164    match level {
3165        WriteCompressionLevel::None => ZlibCompression::default(),
3166        level => zlib_compression(level),
3167    }
3168}
3169
3170fn bzip2_compression(level: WriteCompressionLevel) -> Result<Bzip2Compression> {
3171    match level {
3172        WriteCompressionLevel::Default => Ok(Bzip2Compression::default()),
3173        WriteCompressionLevel::None => Err(EwfError::Unsupported(
3174            "BZip2 writer compression does not support none compression level".into(),
3175        )),
3176        WriteCompressionLevel::Fast => Ok(Bzip2Compression::fast()),
3177        WriteCompressionLevel::Best => Ok(Bzip2Compression::best()),
3178    }
3179}
3180
3181fn ewf2_metadata_section_payload(
3182    payload: &[u8],
3183    compression: WriteCompression,
3184    compression_level: WriteCompressionLevel,
3185) -> Result<Vec<u8>> {
3186    match compression {
3187        WriteCompression::None | WriteCompression::Zlib => {
3188            let mut encoder = ZlibEncoder::new(Vec::new(), zlib_compression(compression_level));
3189            encoder.write_all(payload)?;
3190            Ok(encoder.finish()?)
3191        }
3192        WriteCompression::Bzip2 => {
3193            let mut encoder = BzEncoder::new(Vec::new(), bzip2_compression(compression_level)?);
3194            encoder.write_all(payload)?;
3195            Ok(encoder.finish()?)
3196        }
3197    }
3198}
3199
3200fn ewf2_device_information_section_payload(
3201    payload: &[u8],
3202    options: &WriteOptions,
3203) -> Result<Vec<u8>> {
3204    let compression = if matches!(options.compression, WriteCompression::Bzip2) {
3205        WriteCompression::Zlib
3206    } else {
3207        options.compression
3208    };
3209    ewf2_metadata_section_payload(payload, compression, options.compression_values.level)
3210}
3211
3212fn ewf2_device_information_payload(
3213    options: &WriteOptions,
3214    sector_count: u64,
3215    chunk_count: u64,
3216) -> Vec<u8> {
3217    let physical = u8::from(options.format == WriteFormat::Ewf2Physical);
3218    let mut names = vec![
3219        "sn", "md", "lb", "ts", "hs", "dc", "dt", "pid", "rs", "ls", "bp", "ph", "sc", "tb",
3220    ];
3221    let media_type = options
3222        .media_profile
3223        .media_type
3224        .or(Some(MediaType::Removable))
3225        .map(ewf2_media_type_value)
3226        .map(|value| value.to_string())
3227        .unwrap_or_default();
3228    let mut values = vec![
3229        ewf2_device_header_value(&options.metadata, "serial_number", &["sn"]),
3230        ewf2_device_header_value(&options.metadata, "model", &["md"]),
3231        ewf2_device_header_value(&options.metadata, "device_label", &["lb", "l"]),
3232        sector_count.to_string(),
3233        String::new(),
3234        String::new(),
3235        media_type,
3236        ewf2_device_header_value(&options.metadata, "process_identifier", &["pid"]),
3237        String::new(),
3238        String::new(),
3239        options.bytes_per_sector.to_string(),
3240        physical.to_string(),
3241        options.sectors_per_chunk.to_string(),
3242        chunk_count.to_string(),
3243    ];
3244    if let Some(error_granularity) = options.media_profile.error_granularity {
3245        names.push("gr");
3246        values.push(error_granularity.to_string());
3247    }
3248    let write_blocker_flags =
3249        u64::from(options.media_profile.fastbloc) | (u64::from(options.media_profile.tableau) << 1);
3250    if write_blocker_flags != 0 {
3251        names.push("wb");
3252        values.push(write_blocker_flags.to_string());
3253    }
3254
3255    let text = format!("1\nmain\n{}\n{}\n\n", names.join("\t"), values.join("\t"));
3256    utf16le_with_bom(&text)
3257}
3258
3259fn ewf2_device_header_value(metadata: &EwfMetadata, identifier: &str, aliases: &[&str]) -> String {
3260    metadata
3261        .header_value(identifier)
3262        .or_else(|| {
3263            aliases
3264                .iter()
3265                .find_map(|alias| metadata.header_values.get(*alias).map(String::as_str))
3266        })
3267        .map(sanitize_header_value)
3268        .unwrap_or_default()
3269}
3270
3271fn ewf2_media_type_value(media_type: MediaType) -> char {
3272    match media_type {
3273        MediaType::Removable => 'r',
3274        MediaType::Fixed => 'f',
3275        MediaType::Optical => 'c',
3276        MediaType::SingleFiles => 'l',
3277        MediaType::Memory => 'm',
3278        MediaType::Unknown(value) => char::from(value),
3279    }
3280}
3281
3282fn ewf2_case_data_payload(options: &WriteOptions, chunk_count: u64) -> Vec<u8> {
3283    let metadata = &options.metadata;
3284    let write_blocker_flags =
3285        u64::from(options.media_profile.fastbloc) | (u64::from(options.media_profile.tableau) << 1);
3286    let compression_method = ewf2_case_data_header_value(metadata, "compression_method", &["cp"]);
3287    let error_granularity = options.media_profile.error_granularity.map_or_else(
3288        || {
3289            ewf2_case_data_header_value(metadata, "error_granularity", &["gr"])
3290                .unwrap_or_else(|| "0".to_string())
3291        },
3292        |value| value.to_string(),
3293    );
3294    let write_blocker = if write_blocker_flags != 0 {
3295        write_blocker_flags.to_string()
3296    } else {
3297        ewf2_case_data_header_value(metadata, "write_blocker", &["wb"]).unwrap_or_default()
3298    };
3299    let fields = [
3300        (
3301            "nm",
3302            ewf2_case_data_header_value(metadata, "description", &["nm", "de"]).unwrap_or_default(),
3303        ),
3304        (
3305            "cn",
3306            ewf2_case_data_header_value(metadata, "case_number", &["cn"]).unwrap_or_default(),
3307        ),
3308        (
3309            "en",
3310            ewf2_case_data_header_value(metadata, "evidence_number", &["en"]).unwrap_or_default(),
3311        ),
3312        (
3313            "ex",
3314            ewf2_case_data_header_value(metadata, "examiner_name", &["ex"]).unwrap_or_default(),
3315        ),
3316        (
3317            "nt",
3318            ewf2_case_data_header_value(metadata, "notes", &["nt"]).unwrap_or_default(),
3319        ),
3320        (
3321            "av",
3322            ewf2_case_data_header_value(metadata, "acquiry_software_version", &["av"])
3323                .unwrap_or_default(),
3324        ),
3325        (
3326            "os",
3327            ewf2_case_data_header_value(metadata, "acquiry_operating_system", &["os", "ov"])
3328                .unwrap_or_default(),
3329        ),
3330        (
3331            "tt",
3332            ewf2_case_data_header_value(metadata, "system_date", &["tt", "sd"]).unwrap_or_default(),
3333        ),
3334        (
3335            "at",
3336            ewf2_case_data_header_value(metadata, "acquiry_date", &["at", "ad"])
3337                .unwrap_or_default(),
3338        ),
3339        ("tb", chunk_count.to_string()),
3340        ("cp", compression_method.unwrap_or_default()),
3341        ("sb", options.sectors_per_chunk.to_string()),
3342        ("gr", error_granularity),
3343        ("wb", write_blocker),
3344    ];
3345    let mut names = fields
3346        .iter()
3347        .map(|(name, _)| (*name).to_string())
3348        .collect::<Vec<_>>();
3349    let mut values = fields
3350        .into_iter()
3351        .map(|(_, value)| value)
3352        .collect::<Vec<_>>();
3353    if let Some(acquisition_software) = metadata.acquisition_software.as_deref() {
3354        names.push("acquiry_software".to_string());
3355        values.push(sanitize_header_value(acquisition_software));
3356    }
3357    if let Some(password) = metadata.password.as_deref() {
3358        names.push("password".to_string());
3359        values.push(sanitize_header_value(password));
3360    }
3361    for (name, value) in &metadata.header_values {
3362        let tag = ewf2_case_data_tag(name);
3363        if names.iter().any(|existing| existing == tag) {
3364            continue;
3365        }
3366        names.push(tag.to_string());
3367        values.push(sanitize_header_value(value));
3368    }
3369
3370    let text = format!("1\nmain\n{}\n{}\n\n", names.join("\t"), values.join("\t"));
3371    utf16le_with_bom(&text)
3372}
3373
3374fn ewf2_case_data_header_value(
3375    metadata: &EwfMetadata,
3376    identifier: &str,
3377    aliases: &[&str],
3378) -> Option<String> {
3379    metadata
3380        .header_value(identifier)
3381        .or_else(|| {
3382            aliases
3383                .iter()
3384                .find_map(|alias| metadata.header_values.get(*alias).map(String::as_str))
3385        })
3386        .map(sanitize_header_value)
3387}
3388
3389fn ewf2_case_data_tag(identifier: &str) -> &str {
3390    match identifier {
3391        "acquiry_date" | "ad" => "at",
3392        "acquiry_operating_system" | "ov" => "os",
3393        "acquiry_software_version" => "av",
3394        "case_number" => "cn",
3395        "compression_method" => "cp",
3396        "description" | "de" => "nm",
3397        "evidence_number" => "en",
3398        "examiner_name" => "ex",
3399        "error_granularity" => "gr",
3400        "notes" => "nt",
3401        "number_of_chunks" => "tb",
3402        "sectors_per_chunk" => "sb",
3403        "system_date" | "sd" => "tt",
3404        "write_blocker" => "wb",
3405        _ => identifier,
3406    }
3407}
3408
3409fn ewf2_memory_extents_table_payload(extents: &[MemoryExtent]) -> Option<Vec<u8>> {
3410    if extents.is_empty() {
3411        return None;
3412    }
3413
3414    let mut payload = Vec::with_capacity(extents.len() * 16);
3415    for extent in extents {
3416        payload.extend_from_slice(&extent.start_page.to_le_bytes());
3417        payload.extend_from_slice(&extent.page_count.to_le_bytes());
3418    }
3419    Some(payload)
3420}
3421
3422fn ewf2_single_files_aux_u64_table_payload(entries: &[u64]) -> Option<Vec<u8>> {
3423    if entries.is_empty() {
3424        return None;
3425    }
3426
3427    let mut entry_data = Vec::with_capacity(entries.len() * 8);
3428    for entry in entries {
3429        entry_data.extend_from_slice(&entry.to_le_bytes());
3430    }
3431    Some(ewf2_single_files_aux_table_payload(
3432        entries.len(),
3433        &entry_data,
3434    ))
3435}
3436
3437fn ewf2_single_files_md5_hash_table_payload(hashes: &[[u8; 16]]) -> Option<Vec<u8>> {
3438    if hashes.is_empty() {
3439        return None;
3440    }
3441
3442    let mut entry_data = Vec::with_capacity(hashes.len() * 16);
3443    for hash in hashes {
3444        entry_data.extend_from_slice(hash);
3445    }
3446    Some(ewf2_single_files_aux_table_payload(
3447        hashes.len(),
3448        &entry_data,
3449    ))
3450}
3451
3452fn ewf2_single_files_aux_table_payload(entry_count: usize, entry_data: &[u8]) -> Vec<u8> {
3453    let mut payload = Vec::with_capacity(32 + entry_data.len() + 16);
3454    let entry_count = u32::try_from(entry_count).expect("entry count fits u32");
3455    payload.extend_from_slice(&entry_count.to_le_bytes());
3456    payload.extend_from_slice(&[0; 12]);
3457    let header_checksum = adler32(&payload);
3458    payload.extend_from_slice(&header_checksum.to_le_bytes());
3459    payload.extend_from_slice(&[0; 12]);
3460    payload.extend_from_slice(entry_data);
3461    let entries_checksum = adler32(entry_data);
3462    payload.extend_from_slice(&entries_checksum.to_le_bytes());
3463    payload.extend_from_slice(&[0; 12]);
3464    payload
3465}
3466
3467fn ewf2_string_section_payload(
3468    text: &str,
3469    compression: WriteCompression,
3470    compression_level: WriteCompressionLevel,
3471) -> Result<Vec<u8>> {
3472    ewf2_metadata_section_payload(&utf16le_with_bom(text), compression, compression_level)
3473}
3474
3475fn ewf1_ltree_payload(info: &SingleFilesInfo) -> Result<Vec<u8>> {
3476    let single_files_data = ewf2_single_files_data_payload(info)?;
3477    let mut payload = Vec::with_capacity(EWF1_LTREE_HEADER_SIZE + single_files_data.len());
3478    let mut hasher = Md5::new();
3479    hasher.update(&single_files_data);
3480    payload.extend_from_slice(&hasher.finalize());
3481    payload.extend_from_slice(
3482        &u64::try_from(single_files_data.len())
3483            .expect("usize fits u64")
3484            .to_le_bytes(),
3485    );
3486    payload.extend_from_slice(&0_u32.to_le_bytes());
3487    payload.extend_from_slice(&[0; 20]);
3488    let checksum = adler32(&payload[..EWF1_LTREE_HEADER_SIZE]);
3489    payload[24..28].copy_from_slice(&checksum.to_le_bytes());
3490    payload.extend_from_slice(&single_files_data);
3491    Ok(payload)
3492}
3493
3494fn ewf2_single_files_data_payload(info: &SingleFilesInfo) -> Result<Vec<u8>> {
3495    let source_ids = SingleFileSourceIds::new(&info.sources)?;
3496    let mut lines = Vec::new();
3497    lines.push("5".to_owned());
3498    append_single_file_record_category(&mut lines, info)?;
3499    append_single_file_permission_category(&mut lines, &info.permission_groups);
3500    append_single_file_source_category(&mut lines, &info.sources)?;
3501    append_single_file_subject_category(&mut lines, &info.subjects);
3502    append_single_file_entry_category(&mut lines, &info.root, &source_ids)?;
3503    Ok(utf16le_with_bom(&lines.join("\n")))
3504}
3505
3506struct SingleFileSourceIds {
3507    remapped: BTreeMap<i32, i32>,
3508}
3509
3510impl SingleFileSourceIds {
3511    fn new(sources: &[SingleFileSource]) -> Result<Self> {
3512        let mut remapped = BTreeMap::new();
3513        let children = sources.get(1..).unwrap_or(&[]);
3514        for (index, source) in children.iter().enumerate() {
3515            if let Some(identifier) = source.identifier {
3516                remapped
3517                    .entry(identifier)
3518                    .or_insert(source_child_identifier(index)?);
3519            }
3520        }
3521        Ok(Self { remapped })
3522    }
3523
3524    fn entry_identifier(&self, identifier: Option<i32>) -> Option<i32> {
3525        match identifier {
3526            Some(identifier) if identifier > 0 => {
3527                self.remapped.get(&identifier).copied().or(Some(identifier))
3528            }
3529            _ => identifier,
3530        }
3531    }
3532}
3533
3534fn source_child_identifier(index: usize) -> Result<i32> {
3535    let identifier = index
3536        .checked_add(1)
3537        .ok_or_else(|| EwfError::Malformed("single files source identifier overflow".into()))?;
3538    i32::try_from(identifier).map_err(|_| {
3539        EwfError::Unsupported("single files source count exceeds supported identifier range".into())
3540    })
3541}
3542
3543fn append_single_file_record_category(
3544    lines: &mut Vec<String>,
3545    info: &SingleFilesInfo,
3546) -> Result<()> {
3547    lines.push("rec".to_owned());
3548    lines.push("tb".to_owned());
3549    lines.push(single_files_record_data_size(info)?.to_string());
3550    lines.push(String::new());
3551    Ok(())
3552}
3553
3554fn single_files_record_data_size(info: &SingleFilesInfo) -> Result<u64> {
3555    Ok(info.data_size.max(single_file_entry_data_size(&info.root)?))
3556}
3557
3558fn single_file_entry_data_size(entry: &SingleFileEntry) -> Result<u64> {
3559    let mut data_size = entry.size.unwrap_or(0);
3560    for extent in &entry.extents {
3561        if extent.sparse {
3562            continue;
3563        }
3564        let extent_end = extent
3565            .data_offset
3566            .checked_add(extent.data_size)
3567            .ok_or_else(|| EwfError::Malformed("single files extent end overflow".into()))?;
3568        data_size = data_size.max(extent_end);
3569    }
3570    for child in &entry.children {
3571        data_size = data_size.max(single_file_entry_data_size(child)?);
3572    }
3573    Ok(data_size)
3574}
3575
3576fn append_single_file_entry_category(
3577    lines: &mut Vec<String>,
3578    root: &SingleFileEntry,
3579    source_ids: &SingleFileSourceIds,
3580) -> Result<()> {
3581    let include_guid = single_file_entry_tree_has_guid(root);
3582    let entry_types = single_file_entry_types(include_guid);
3583    lines.push("entry".to_owned());
3584    lines.push("0\t1".to_owned());
3585    lines.push(entry_types.join("\t"));
3586    append_single_file_entry(lines, root, source_ids, include_guid)?;
3587    lines.push(String::new());
3588    Ok(())
3589}
3590
3591fn single_file_entry_types(include_guid: bool) -> Vec<&'static str> {
3592    let mut entry_types = SINGLE_FILE_ENTRY_TYPES.to_vec();
3593    if include_guid {
3594        entry_types.push("mid");
3595    }
3596    entry_types
3597}
3598
3599fn single_file_entry_tree_has_guid(entry: &SingleFileEntry) -> bool {
3600    entry.guid.is_some() || entry.children.iter().any(single_file_entry_tree_has_guid)
3601}
3602
3603fn append_single_file_entry(
3604    lines: &mut Vec<String>,
3605    entry: &SingleFileEntry,
3606    source_ids: &SingleFileSourceIds,
3607    include_guid: bool,
3608) -> Result<()> {
3609    lines.push(format!("26\t{}", entry.children.len()));
3610    lines.push(single_file_entry_row(entry, source_ids, include_guid)?);
3611    for child in &entry.children {
3612        append_single_file_entry(lines, child, source_ids, include_guid)?;
3613    }
3614    Ok(())
3615}
3616
3617fn single_file_entry_row(
3618    entry: &SingleFileEntry,
3619    source_ids: &SingleFileSourceIds,
3620    include_guid: bool,
3621) -> Result<String> {
3622    let mut values = vec![
3623        optional_display(entry.identifier),
3624        single_file_entry_type_value(entry.file_entry_type),
3625        optional_text(entry.name.as_deref()),
3626        optional_display(entry.size),
3627        optional_display(entry.logical_offset),
3628        optional_display(entry.physical_offset),
3629        optional_display(entry.duplicate_data_offset),
3630        optional_display(source_ids.entry_identifier(entry.source_identifier)),
3631        optional_display(entry.subject_identifier),
3632        optional_display(entry.permission_group_index),
3633        optional_display(entry.record_type),
3634        optional_display(entry.flags),
3635        single_file_extents_value(&entry.extents),
3636        optional_text(entry.md5.as_deref()),
3637        optional_text(entry.sha1.as_deref()),
3638        single_file_short_name_value(entry.short_name.as_deref()),
3639        optional_display(entry.creation_time),
3640        optional_display(entry.modification_time),
3641        optional_display(entry.access_time),
3642        optional_display(entry.entry_modification_time),
3643        optional_display(entry.deletion_time),
3644        single_file_attributes_value(&entry.attributes)?,
3645    ];
3646    if include_guid {
3647        values.push(optional_text(entry.guid.as_deref()));
3648    }
3649    Ok(values.join("\t"))
3650}
3651
3652fn append_single_file_source_category(
3653    lines: &mut Vec<String>,
3654    sources: &[SingleFileSource],
3655) -> Result<()> {
3656    let default_root = SingleFileSource {
3657        identifier: Some(0),
3658        ..SingleFileSource::default()
3659    };
3660    let (root, children) = sources.split_first().unwrap_or((&default_root, &[]));
3661    lines.push("srce".to_owned());
3662    lines.push(format!("{}\t1", children.len()));
3663    lines.push(SINGLE_FILE_SOURCE_TYPES.join("\t"));
3664    lines.push(format!("0\t{}", children.len()));
3665    lines.push(single_file_source_row(root, Some(0)));
3666    for (index, source) in children.iter().enumerate() {
3667        lines.push("0\t0".to_owned());
3668        lines.push(single_file_source_row(
3669            source,
3670            Some(source_child_identifier(index)?),
3671        ));
3672    }
3673    lines.push(String::new());
3674    Ok(())
3675}
3676
3677fn single_file_source_row(source: &SingleFileSource, identifier: Option<i32>) -> String {
3678    vec![
3679        optional_display(identifier.or(source.identifier)),
3680        optional_text(source.name.as_deref()),
3681        optional_text(source.evidence_number.as_deref()),
3682        optional_text(source.location.as_deref()),
3683        optional_text(source.device_guid.as_deref()),
3684        optional_text(source.primary_device_guid.as_deref()),
3685        source
3686            .drive_type
3687            .map(|value| value.to_string())
3688            .unwrap_or_default(),
3689        optional_text(source.manufacturer.as_deref()),
3690        optional_text(source.model.as_deref()),
3691        optional_text(source.serial_number.as_deref()),
3692        optional_text(source.domain.as_deref()),
3693        optional_text(source.ip_address.as_deref()),
3694        optional_text(source.mac_address.as_deref()),
3695        optional_display(source.size),
3696        optional_display(source.logical_offset),
3697        optional_display(source.physical_offset),
3698        optional_display(source.acquisition_time),
3699        optional_text(source.md5.as_deref()),
3700        optional_text(source.sha1.as_deref()),
3701    ]
3702    .join("\t")
3703}
3704
3705fn append_single_file_subject_category(lines: &mut Vec<String>, subjects: &[SingleFileSubject]) {
3706    let default_root = SingleFileSubject {
3707        identifier: Some(0),
3708        ..SingleFileSubject::default()
3709    };
3710    let (root, children) = subjects.split_first().unwrap_or((&default_root, &[]));
3711    lines.push("sub".to_owned());
3712    lines.push(format!("{}\t1", children.len()));
3713    lines.push(SINGLE_FILE_SUBJECT_TYPES.join("\t"));
3714    lines.push(format!("0\t{}", children.len()));
3715    lines.push(single_file_subject_row(root));
3716    for subject in children {
3717        lines.push("0\t0".to_owned());
3718        lines.push(single_file_subject_row(subject));
3719    }
3720    lines.push(String::new());
3721}
3722
3723fn single_file_subject_row(subject: &SingleFileSubject) -> String {
3724    [
3725        optional_display(subject.identifier),
3726        optional_text(subject.name.as_deref()),
3727    ]
3728    .join("\t")
3729}
3730
3731fn append_single_file_permission_category(
3732    lines: &mut Vec<String>,
3733    groups: &[SingleFilePermissionGroup],
3734) {
3735    lines.push("perm".to_owned());
3736    lines.push(format!("{}\t1", groups.len()));
3737    lines.push(SINGLE_FILE_PERMISSION_TYPES.join("\t"));
3738    lines.push(format!("0\t{}", groups.len()));
3739    lines.push(single_file_permission_root_row());
3740    for group in groups {
3741        lines.push(format!("0\t{}", group.permissions.len()));
3742        lines.push(single_file_permission_group_row(group));
3743        for permission in &group.permissions {
3744            lines.push("0\t0".to_owned());
3745            lines.push(single_file_permission_row(permission));
3746        }
3747    }
3748    lines.push(String::new());
3749}
3750
3751fn single_file_permission_root_row() -> String {
3752    single_file_permission_row(&SingleFilePermission {
3753        property_type: Some(10),
3754        ..SingleFilePermission::default()
3755    })
3756}
3757
3758fn single_file_permission_group_row(group: &SingleFilePermissionGroup) -> String {
3759    [
3760        optional_text(group.name.as_deref()),
3761        optional_display(group.property_type.or(Some(10))),
3762        optional_text(group.identifier.as_deref()),
3763        optional_display(group.access_mask),
3764        optional_display(group.ace_flags),
3765    ]
3766    .join("\t")
3767}
3768
3769fn single_file_permission_row(permission: &SingleFilePermission) -> String {
3770    [
3771        optional_text(permission.name.as_deref()),
3772        optional_display(permission.property_type),
3773        optional_text(permission.identifier.as_deref()),
3774        optional_display(permission.access_mask),
3775        optional_display(permission.ace_flags),
3776    ]
3777    .join("\t")
3778}
3779
3780fn single_file_entry_type_value(value: Option<SingleFileEntryType>) -> String {
3781    match value {
3782        Some(SingleFileEntryType::File) => "f".to_owned(),
3783        Some(SingleFileEntryType::Directory) => "d".to_owned(),
3784        Some(SingleFileEntryType::Unknown) => "u".to_owned(),
3785        None => String::new(),
3786    }
3787}
3788
3789fn single_file_extents_value(extents: &[SingleFileExtent]) -> String {
3790    if extents.is_empty() {
3791        return String::new();
3792    }
3793
3794    let mut parts = Vec::with_capacity(1 + extents.len() * 3);
3795    parts.push(format!("{:x}", extents.len()));
3796    for extent in extents {
3797        if extent.sparse {
3798            parts.push("S".to_owned());
3799        }
3800        parts.push(format!("{:x}", extent.data_offset));
3801        parts.push(format!("{:x}", extent.data_size));
3802    }
3803    parts.join(" ")
3804}
3805
3806fn single_file_attributes_value(attributes: &[SingleFileAttribute]) -> Result<String> {
3807    if attributes.is_empty() {
3808        return Ok(String::new());
3809    }
3810
3811    let mut data = EWF2_EXTENDED_ATTRIBUTES_HEADER.to_vec();
3812    for attribute in attributes {
3813        let name = optional_utf16le_null_terminated(attribute.name.as_deref());
3814        let value = optional_utf16le_null_terminated(attribute.value.as_deref());
3815        let name_units = u32::try_from(name.len() / 2).map_err(|_| {
3816            EwfError::Unsupported("single file attribute name length exceeds u32".into())
3817        })?;
3818        let value_units = u32::try_from(value.len() / 2).map_err(|_| {
3819            EwfError::Unsupported("single file attribute value length exceeds u32".into())
3820        })?;
3821
3822        data.extend_from_slice(&[0; 4]);
3823        data.push(0);
3824        data.extend_from_slice(&name_units.to_le_bytes());
3825        data.extend_from_slice(&value_units.to_le_bytes());
3826        data.extend_from_slice(&name);
3827        data.extend_from_slice(&value);
3828    }
3829    Ok(hex_bytes(&data))
3830}
3831
3832fn optional_utf16le_null_terminated(value: Option<&str>) -> Vec<u8> {
3833    let Some(value) = value else {
3834        return Vec::new();
3835    };
3836
3837    let mut bytes = utf16le(&optional_text(Some(value)));
3838    bytes.extend_from_slice(&0_u16.to_le_bytes());
3839    bytes
3840}
3841
3842fn optional_text(value: Option<&str>) -> String {
3843    value.map(sanitize_header_value).unwrap_or_default()
3844}
3845
3846fn single_file_short_name_value(value: Option<&str>) -> String {
3847    let value = optional_text(value);
3848    if value.is_empty() {
3849        String::new()
3850    } else {
3851        format!("{} {value}", value.len() + 1)
3852    }
3853}
3854
3855fn optional_display<T: std::fmt::Display>(value: Option<T>) -> String {
3856    value.map(|value| value.to_string()).unwrap_or_default()
3857}
3858
3859fn hex_bytes(bytes: &[u8]) -> String {
3860    const HEX: &[u8; 16] = b"0123456789abcdef";
3861    let mut out = String::with_capacity(bytes.len() * 2);
3862    for byte in bytes {
3863        out.push(char::from(HEX[(byte >> 4) as usize]));
3864        out.push(char::from(HEX[(byte & 0x0f) as usize]));
3865    }
3866    out
3867}
3868
3869fn ewf2_hash_payload<const N: usize>(hash: [u8; N]) -> [u8; 32] {
3870    let mut payload = [0; 32];
3871    payload[..N].copy_from_slice(&hash);
3872    let checksum = adler32(&payload[..N]);
3873    payload[N..N + 4].copy_from_slice(&checksum.to_le_bytes());
3874    payload
3875}
3876
3877fn ewf1_error2_payload(errors: &[AcquisitionError]) -> Result<Option<Vec<u8>>> {
3878    if errors.is_empty() {
3879        return Ok(None);
3880    }
3881
3882    let entry_count = u32::try_from(errors.len()).map_err(|_| {
3883        EwfError::Unsupported("EWF1 writer acquisition error count exceeds u32".into())
3884    })?;
3885    let mut payload = Vec::with_capacity(520 + errors.len() * 8 + 4);
3886    payload.resize(520, 0);
3887    payload[0..4].copy_from_slice(&entry_count.to_le_bytes());
3888    let header_checksum = adler32(&payload[..516]);
3889    payload[516..520].copy_from_slice(&header_checksum.to_le_bytes());
3890    let entries_start = payload.len();
3891    for error in errors {
3892        let first_sector = u32::try_from(error.first_sector).map_err(|_| {
3893            EwfError::Unsupported("EWF1 writer acquisition error first sector exceeds u32".into())
3894        })?;
3895        let sector_count = u32::try_from(error.sector_count).map_err(|_| {
3896            EwfError::Unsupported("EWF1 writer acquisition error sector count exceeds u32".into())
3897        })?;
3898        payload.extend_from_slice(&first_sector.to_le_bytes());
3899        payload.extend_from_slice(&sector_count.to_le_bytes());
3900    }
3901    let entries_checksum = adler32(&payload[entries_start..]);
3902    payload.extend_from_slice(&entries_checksum.to_le_bytes());
3903    Ok(Some(payload))
3904}
3905
3906fn ewf2_error_table_payload(errors: &[AcquisitionError]) -> Result<Option<Vec<u8>>> {
3907    if errors.is_empty() {
3908        return Ok(None);
3909    }
3910
3911    let entry_count = u32::try_from(errors.len()).map_err(|_| {
3912        EwfError::Unsupported("EWF2 writer acquisition error count exceeds u32".into())
3913    })?;
3914    let mut payload = vec![0; 32];
3915    payload[0..4].copy_from_slice(&entry_count.to_le_bytes());
3916    let header_checksum = adler32(&payload[..16]);
3917    payload[16..20].copy_from_slice(&header_checksum.to_le_bytes());
3918    let entries_start = payload.len();
3919    for error in errors {
3920        let sector_count = u32::try_from(error.sector_count).map_err(|_| {
3921            EwfError::Unsupported("EWF2 writer acquisition error sector count exceeds u32".into())
3922        })?;
3923        payload.extend_from_slice(&error.first_sector.to_le_bytes());
3924        payload.extend_from_slice(&sector_count.to_le_bytes());
3925        payload.extend_from_slice(&[0; 4]);
3926    }
3927    let entries_checksum = adler32(&payload[entries_start..]);
3928    payload.extend_from_slice(&entries_checksum.to_le_bytes());
3929    payload.extend_from_slice(&[0; 12]);
3930    Ok(Some(payload))
3931}
3932
3933fn ewf1_session_payload(
3934    sessions: &[SectorRange],
3935    tracks: &[SectorRange],
3936) -> Result<Option<Vec<u8>>> {
3937    session_payload(1, sessions, tracks)
3938}
3939
3940fn ewf2_session_table_payload(
3941    sessions: &[SectorRange],
3942    tracks: &[SectorRange],
3943) -> Result<Option<Vec<u8>>> {
3944    session_payload(2, sessions, tracks)
3945}
3946
3947fn session_payload(
3948    format_version: u8,
3949    sessions: &[SectorRange],
3950    tracks: &[SectorRange],
3951) -> Result<Option<Vec<u8>>> {
3952    const AUDIO_TRACK_FLAG: u32 = 0x01;
3953
3954    if sessions.is_empty() && tracks.is_empty() {
3955        return Ok(None);
3956    }
3957
3958    let mut entries = Vec::with_capacity(sessions.len() + tracks.len());
3959    entries.extend(sessions.iter().map(|range| (range.first_sector, 0)));
3960    entries.extend(
3961        tracks
3962            .iter()
3963            .map(|range| (range.first_sector, AUDIO_TRACK_FLAG)),
3964    );
3965    entries.sort_by_key(|(start_sector, flags)| (*start_sector, *flags & AUDIO_TRACK_FLAG));
3966
3967    let entry_count = u32::try_from(entries.len())
3968        .map_err(|_| EwfError::Unsupported("writer session entry count exceeds u32".into()))?;
3969    let (header_size, checksum_offset, checksum_data_size, footer_size) = match format_version {
3970        1 => (36_usize, 32_usize, 32_usize, 4_usize),
3971        2 => (32_usize, 16_usize, 16_usize, 16_usize),
3972        _ => unreachable!("writer only emits EWF1 or EWF2 sessions"),
3973    };
3974    let mut payload = vec![0; header_size];
3975    payload[0..4].copy_from_slice(&entry_count.to_le_bytes());
3976    let header_checksum = adler32(&payload[..checksum_data_size]);
3977    payload[checksum_offset..checksum_offset + 4].copy_from_slice(&header_checksum.to_le_bytes());
3978
3979    let entries_start = payload.len();
3980    for (start_sector, flags) in entries {
3981        if format_version == 1 {
3982            let start_sector = u32::try_from(start_sector).map_err(|_| {
3983                EwfError::Unsupported("EWF1 writer session start sector exceeds u32".into())
3984            })?;
3985            payload.extend_from_slice(&flags.to_le_bytes());
3986            payload.extend_from_slice(&start_sector.to_le_bytes());
3987            payload.extend_from_slice(&[0; 24]);
3988        } else {
3989            payload.extend_from_slice(&start_sector.to_le_bytes());
3990            payload.extend_from_slice(&flags.to_le_bytes());
3991            payload.extend_from_slice(&[0; 20]);
3992        }
3993    }
3994    let entries_checksum = adler32(&payload[entries_start..]);
3995    payload.extend_from_slice(&entries_checksum.to_le_bytes());
3996    if footer_size > 4 {
3997        payload.extend_from_slice(&vec![0; footer_size - 4]);
3998    }
3999
4000    Ok(Some(payload))
4001}
4002
4003fn adler32(data: &[u8]) -> u32 {
4004    const MOD_ADLER: u32 = 65_521;
4005    let mut a = 1_u32;
4006    let mut b = 0_u32;
4007    for byte in data {
4008        a = (a + u32::from(*byte)) % MOD_ADLER;
4009        b = (b + a) % MOD_ADLER;
4010    }
4011    (b << 16) | a
4012}
4013
4014fn utf16le(text: &str) -> Vec<u8> {
4015    let mut bytes = Vec::with_capacity(text.len() * 2);
4016    for unit in text.encode_utf16() {
4017        bytes.extend_from_slice(&unit.to_le_bytes());
4018    }
4019    bytes
4020}
4021
4022fn utf16le_with_bom(text: &str) -> Vec<u8> {
4023    let mut bytes = Vec::with_capacity(2 + text.len() * 2);
4024    bytes.extend_from_slice(&0xfeff_u16.to_le_bytes());
4025    bytes.extend_from_slice(&utf16le(text));
4026    bytes
4027}
4028
4029fn ewf2_table_header(first_chunk: u64, chunk_count: u32) -> [u8; EWF2_TABLE_HEADER_V2_SIZE] {
4030    let mut table = [0; EWF2_TABLE_HEADER_V2_SIZE];
4031    table[0..8].copy_from_slice(&first_chunk.to_le_bytes());
4032    table[8..12].copy_from_slice(&chunk_count.to_le_bytes());
4033    let checksum = adler32(&table[..16]);
4034    table[16..20].copy_from_slice(&checksum.to_le_bytes());
4035    table
4036}
4037
4038fn ewf2_table_entry(
4039    chunk_offset: u64,
4040    chunk_size: u32,
4041    compressed: bool,
4042    has_checksum: bool,
4043    pattern_fill: bool,
4044) -> [u8; ewf2::TABLE_ENTRY_SIZE] {
4045    let mut entry = [0; ewf2::TABLE_ENTRY_SIZE];
4046    entry[0..8].copy_from_slice(&chunk_offset.to_le_bytes());
4047    entry[8..12].copy_from_slice(&chunk_size.to_le_bytes());
4048    let mut flags = 0_u32;
4049    if compressed {
4050        flags |= ewf2::CHUNK_FLAG_COMPRESSED;
4051    }
4052    if has_checksum {
4053        flags |= ewf2::CHUNK_FLAG_HAS_CHECKSUM;
4054    }
4055    if pattern_fill {
4056        flags |= ewf2::CHUNK_FLAG_PATTERN_FILL;
4057    }
4058    entry[12..16].copy_from_slice(&flags.to_le_bytes());
4059    entry
4060}
4061
4062fn ewf2_section_desc(
4063    section_type: u32,
4064    data_size: u64,
4065    previous_offset: u64,
4066) -> [u8; ewf2::SECTION_DESCRIPTOR_SIZE] {
4067    let mut desc = [0; ewf2::SECTION_DESCRIPTOR_SIZE];
4068    desc[0..4].copy_from_slice(&section_type.to_le_bytes());
4069    desc[8..16].copy_from_slice(&previous_offset.to_le_bytes());
4070    desc[16..24].copy_from_slice(&data_size.to_le_bytes());
4071    desc[24..28].copy_from_slice(&(ewf2::SECTION_DESCRIPTOR_SIZE as u32).to_le_bytes());
4072    let checksum = adler32(&desc[..ewf2::SECTION_DESCRIPTOR_SIZE - 4]);
4073    desc[ewf2::SECTION_DESCRIPTOR_SIZE - 4..].copy_from_slice(&checksum.to_le_bytes());
4074    desc
4075}
4076
4077fn checked_add(left: u64, right: u64, label: &str) -> Result<u64> {
4078    left.checked_add(right)
4079        .ok_or_else(|| EwfError::Malformed(format!("writer {label} offset overflow")))
4080}
4081
4082fn effective_write_hashes(
4083    requested: &WriteHashes,
4084    computed_md5: [u8; 16],
4085    computed_sha1: [u8; 20],
4086) -> WriteHashes {
4087    let mut hashes = requested.clone();
4088    hashes.md5 = hashes.md5.or(Some(computed_md5));
4089    hashes.sha1 = hashes.sha1.or(Some(computed_sha1));
4090    hashes
4091}
4092
4093fn checked_writer_seek(
4094    current_offset: u64,
4095    logical_input_size: u64,
4096    position: SeekFrom,
4097) -> Result<u64> {
4098    let next = match position {
4099        SeekFrom::Start(offset) => return Ok(offset),
4100        SeekFrom::Current(offset) => i128::from(current_offset) + i128::from(offset),
4101        SeekFrom::End(offset) => i128::from(logical_input_size) + i128::from(offset),
4102    };
4103    if next < 0 {
4104        return Err(EwfError::Malformed(
4105            "writer seek before start of media".into(),
4106        ));
4107    }
4108    u64::try_from(next)
4109        .map_err(|_| EwfError::Malformed("writer seek offset does not fit u64".into()))
4110}
4111
4112fn encode_raw_spool(
4113    raw: &mut RawSpool,
4114    mut encoded_chunks: BTreeMap<u64, RememberedEncodedChunk>,
4115    logical_size: u64,
4116    chunk_capacity: usize,
4117    chunk_size: u64,
4118    options: &WriteOptions,
4119) -> Result<EncodedSpool> {
4120    let mut chunks = Vec::new();
4121    let mut encoded_spool = ChunkSpool::new()?;
4122    let mut hash_state = WriteHashState::new();
4123    let mut offset = 0_u64;
4124    let is_ewf2 = is_ewf2_format(options.format);
4125
4126    while offset < logical_size {
4127        let take = usize::try_from((logical_size - offset).min(chunk_capacity as u64))
4128            .expect("chunk read is bounded by chunk capacity");
4129        let mut chunk = vec![0; take];
4130        raw.read_at_filling_zeroes(offset, &mut chunk)?;
4131        hash_state.update(&chunk);
4132        let chunk_index = offset / chunk_size;
4133        let encoded = if let Some(remembered) = encoded_chunks.remove(&chunk_index) {
4134            if remembered.logical_size == take
4135                && encoded_data_chunk_encoding_compatible(remembered.encoding, options)
4136            {
4137                remembered.encoded
4138            } else {
4139                encode_chunk(
4140                    chunk,
4141                    options.compression,
4142                    options.compression_values,
4143                    chunk_size,
4144                    is_ewf2,
4145                    !is_ewf2 && options.compression_values.empty_block,
4146                )?
4147            }
4148        } else {
4149            encode_chunk(
4150                chunk,
4151                options.compression,
4152                options.compression_values,
4153                chunk_size,
4154                is_ewf2,
4155                !is_ewf2 && options.compression_values.empty_block,
4156            )?
4157        };
4158        let descriptor = encoded_spool.append(encoded)?;
4159        chunks.push(descriptor);
4160        offset = offset
4161            .checked_add(u64::try_from(take).expect("usize fits u64"))
4162            .ok_or_else(|| EwfError::Malformed("writer raw spool encode offset overflow".into()))?;
4163    }
4164
4165    let (computed_md5, computed_sha1) = hash_state.finalize();
4166    Ok(EncodedSpool {
4167        chunks,
4168        spool: encoded_spool,
4169        computed_md5,
4170        computed_sha1,
4171    })
4172}
4173
4174struct EncodedSpool {
4175    chunks: Vec<ChunkDescriptor>,
4176    spool: ChunkSpool,
4177    computed_md5: [u8; 16],
4178    computed_sha1: [u8; 20],
4179}
4180
4181struct RememberedEncodedChunk {
4182    logical_size: usize,
4183    encoding: DataChunkEncoding,
4184    encoded: EncodedChunk,
4185}
4186
4187#[derive(Debug, Clone, Copy)]
4188struct ChunkDescriptor {
4189    data_offset: u64,
4190    data_size: u64,
4191    compressed: bool,
4192    has_checksum: bool,
4193    pattern_fill: Option<u64>,
4194}
4195
4196struct RawSpool {
4197    file: NamedTempFile,
4198    len: u64,
4199}
4200
4201impl RawSpool {
4202    fn new() -> Result<Self> {
4203        Ok(Self {
4204            file: NamedTempFile::new()?,
4205            len: 0,
4206        })
4207    }
4208
4209    fn len(&self) -> u64 {
4210        self.len
4211    }
4212
4213    fn write_at(&mut self, offset: u64, data: &[u8]) -> Result<()> {
4214        if data.is_empty() {
4215            return Ok(());
4216        }
4217
4218        let data_len = u64::try_from(data.len())
4219            .map_err(|_| EwfError::Malformed("writer data length does not fit u64".into()))?;
4220        let end_offset = offset
4221            .checked_add(data_len)
4222            .ok_or_else(|| EwfError::Malformed("writer raw spool size overflow".into()))?;
4223        let file = self.file.as_file_mut();
4224        file.seek(SeekFrom::Start(offset))?;
4225        file.write_all(data)?;
4226        self.len = self.len.max(end_offset);
4227        Ok(())
4228    }
4229
4230    fn read_at_filling_zeroes(&mut self, offset: u64, buffer: &mut [u8]) -> Result<()> {
4231        buffer.fill(0);
4232        if buffer.is_empty() || offset >= self.len {
4233            return Ok(());
4234        }
4235
4236        let available = self.len - offset;
4237        let take = usize::try_from(available.min(buffer.len() as u64))
4238            .expect("raw spool read is bounded by buffer length");
4239        let file = self.file.as_file_mut();
4240        file.seek(SeekFrom::Start(offset))?;
4241        file.read_exact(&mut buffer[..take])?;
4242        Ok(())
4243    }
4244}
4245
4246struct ChunkSpool {
4247    file: NamedTempFile,
4248    len: u64,
4249}
4250
4251impl ChunkSpool {
4252    fn new() -> Result<Self> {
4253        Ok(Self {
4254            file: NamedTempFile::new()?,
4255            len: 0,
4256        })
4257    }
4258
4259    fn append(&mut self, encoded: EncodedChunk) -> Result<ChunkDescriptor> {
4260        let EncodedChunk {
4261            bytes,
4262            compressed,
4263            has_checksum,
4264            pattern_fill,
4265        } = encoded;
4266        let data_offset = self.len;
4267        let data_size = u64::try_from(bytes.len()).map_err(|_| {
4268            EwfError::Malformed("writer encoded chunk size does not fit u64".into())
4269        })?;
4270        if data_size > 0 {
4271            let file = self.file.as_file_mut();
4272            file.seek(SeekFrom::Start(data_offset))?;
4273            file.write_all(&bytes)?;
4274        }
4275        self.len = self.len.checked_add(data_size).ok_or_else(|| {
4276            EwfError::Malformed("writer encoded chunk spool size overflow".into())
4277        })?;
4278
4279        Ok(ChunkDescriptor {
4280            data_offset,
4281            data_size,
4282            compressed,
4283            has_checksum,
4284            pattern_fill,
4285        })
4286    }
4287
4288    fn copy_chunk_to<W: Write>(
4289        &mut self,
4290        descriptor: &ChunkDescriptor,
4291        writer: &mut W,
4292    ) -> Result<()> {
4293        if descriptor.data_size == 0 {
4294            return Ok(());
4295        }
4296
4297        let mut remaining = descriptor.data_size;
4298        let mut buffer = [0; 8192];
4299        let file = self.file.as_file_mut();
4300        file.seek(SeekFrom::Start(descriptor.data_offset))?;
4301        while remaining > 0 {
4302            let take = usize::try_from(remaining.min(buffer.len() as u64))
4303                .expect("copy chunk is bounded by buffer length");
4304            file.read_exact(&mut buffer[..take])?;
4305            writer.write_all(&buffer[..take])?;
4306            remaining -= u64::try_from(take).expect("usize fits u64");
4307        }
4308
4309        Ok(())
4310    }
4311}
4312
4313struct EncodedChunk {
4314    bytes: Vec<u8>,
4315    compressed: bool,
4316    has_checksum: bool,
4317    pattern_fill: Option<u64>,
4318}
4319
4320fn encode_chunk(
4321    chunk: Vec<u8>,
4322    compression: WriteCompression,
4323    compression_values: WriteCompressionValues,
4324    chunk_size: u64,
4325    allow_pattern_fill: bool,
4326    allow_empty_block_compression: bool,
4327) -> Result<EncodedChunk> {
4328    if allow_pattern_fill && let Some(pattern) = repeated_ewf2_pattern(&chunk) {
4329        return Ok(EncodedChunk {
4330            bytes: Vec::new(),
4331            compressed: true,
4332            has_checksum: false,
4333            pattern_fill: Some(pattern),
4334        });
4335    }
4336
4337    if allow_empty_block_compression && is_full_zero_chunk(&chunk, chunk_size) {
4338        let mut encoder = ZlibEncoder::new(
4339            Vec::new(),
4340            empty_block_zlib_compression(compression_values.level),
4341        );
4342        encoder.write_all(&chunk)?;
4343        return Ok(EncodedChunk {
4344            bytes: encoder.finish()?,
4345            compressed: true,
4346            has_checksum: false,
4347            pattern_fill: None,
4348        });
4349    }
4350
4351    match compression {
4352        WriteCompression::None => {
4353            let checksum = adler32(&chunk);
4354            let mut bytes = chunk;
4355            bytes.extend_from_slice(&checksum.to_le_bytes());
4356            Ok(EncodedChunk {
4357                bytes,
4358                compressed: false,
4359                has_checksum: true,
4360                pattern_fill: None,
4361            })
4362        }
4363        WriteCompression::Zlib => {
4364            let mut encoder =
4365                ZlibEncoder::new(Vec::new(), zlib_compression(compression_values.level));
4366            encoder.write_all(&chunk)?;
4367            Ok(EncodedChunk {
4368                bytes: encoder.finish()?,
4369                compressed: true,
4370                has_checksum: false,
4371                pattern_fill: None,
4372            })
4373        }
4374        WriteCompression::Bzip2 => {
4375            let mut encoder =
4376                BzEncoder::new(Vec::new(), bzip2_compression(compression_values.level)?);
4377            encoder.write_all(&chunk)?;
4378            Ok(EncodedChunk {
4379                bytes: encoder.finish()?,
4380                compressed: true,
4381                has_checksum: false,
4382                pattern_fill: None,
4383            })
4384        }
4385    }
4386}
4387
4388fn is_full_zero_chunk(chunk: &[u8], chunk_size: u64) -> bool {
4389    u64::try_from(chunk.len()).ok() == Some(chunk_size) && chunk.iter().all(|byte| *byte == 0)
4390}
4391
4392fn repeated_ewf2_pattern(chunk: &[u8]) -> Option<u64> {
4393    if chunk.is_empty() {
4394        return None;
4395    }
4396
4397    let mut pattern = [0; 8];
4398    let seed_size = chunk.len().min(pattern.len());
4399    pattern[..seed_size].copy_from_slice(&chunk[..seed_size]);
4400    chunk
4401        .iter()
4402        .enumerate()
4403        .all(|(index, byte)| *byte == pattern[index % pattern.len()])
4404        .then(|| u64::from_le_bytes(pattern))
4405}
4406
4407fn segment_groups(
4408    chunks: Vec<ChunkDescriptor>,
4409    maximum_segment_size: Option<u64>,
4410    options: &WriteOptions,
4411    sector_count: u64,
4412    total_chunk_count: u64,
4413) -> Result<Vec<Vec<ChunkDescriptor>>> {
4414    if chunks.is_empty() {
4415        return Ok(vec![chunks]);
4416    }
4417    let Some(maximum_segment_size) = maximum_segment_size else {
4418        return Ok(vec![chunks]);
4419    };
4420
4421    let mut groups = Vec::new();
4422    let mut current = Vec::new();
4423    let mut payload_size = 0_u64;
4424    for chunk in chunks {
4425        let chunk_size = chunk.data_size;
4426        let proposed_payload_size = payload_size
4427            .checked_add(chunk_size)
4428            .ok_or_else(|| EwfError::Malformed("writer segment payload size overflow".into()))?;
4429        let proposed_size = estimated_segment_size(
4430            current.len() + 1,
4431            proposed_payload_size,
4432            options,
4433            sector_count,
4434            total_chunk_count,
4435        )?;
4436        if !current.is_empty() && proposed_size > maximum_segment_size {
4437            groups.push(current);
4438            current = Vec::new();
4439            payload_size = 0;
4440        }
4441
4442        payload_size = payload_size
4443            .checked_add(chunk_size)
4444            .ok_or_else(|| EwfError::Malformed("writer segment payload size overflow".into()))?;
4445        current.push(chunk);
4446    }
4447
4448    if !current.is_empty() {
4449        groups.push(current);
4450    }
4451    Ok(groups)
4452}
4453
4454fn estimated_segment_size(
4455    chunk_count: usize,
4456    chunk_payload_size: u64,
4457    options: &WriteOptions,
4458    sector_count: u64,
4459    total_chunk_count: u64,
4460) -> Result<u64> {
4461    if is_ewf2_format(options.format) {
4462        return estimated_ewf2_segment_size(
4463            chunk_count,
4464            chunk_payload_size,
4465            options,
4466            sector_count,
4467            total_chunk_count,
4468        );
4469    }
4470    estimated_ewf1_segment_size(chunk_count, chunk_payload_size, options)
4471}
4472
4473fn estimated_ewf1_segment_size(
4474    chunk_count: usize,
4475    chunk_payload_size: u64,
4476    options: &WriteOptions,
4477) -> Result<u64> {
4478    let table_entry_bytes = u64::try_from(chunk_count)
4479        .map_err(|_| EwfError::Malformed("writer segment chunk count does not fit u64".into()))?
4480        .checked_mul(4)
4481        .ok_or_else(|| EwfError::Malformed("writer table entry size overflow".into()))?;
4482    let table_footer_bytes = if matches!(options.format, WriteFormat::Ewf1Smart) {
4483        0
4484    } else {
4485        4
4486    };
4487    let table_data_size = 24_u64
4488        .checked_add(table_entry_bytes)
4489        .and_then(|value| value.checked_add(table_footer_bytes))
4490        .ok_or_else(|| EwfError::Malformed("writer table data size overflow".into()))?;
4491    let writes_table2 = matches!(
4492        options.format,
4493        WriteFormat::Ewf1Physical | WriteFormat::Ewf1Logical
4494    );
4495
4496    let header_size = header_payload(
4497        &options.metadata,
4498        options.header_codepage,
4499        options.compression_values.level,
4500    )?
4501    .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4502    .transpose()?
4503    .unwrap_or(0);
4504    let header2_size = header2_payload(&options.metadata, options.compression_values.level)?
4505        .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4506        .transpose()?
4507        .unwrap_or(0);
4508    let xheader_size = xheader_payload(&options.metadata, options.compression_values.level)?
4509        .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4510        .transpose()?
4511        .unwrap_or(0);
4512    let session_size = ewf1_session_payload(&options.sessions, &options.tracks)?
4513        .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4514        .transpose()?
4515        .unwrap_or(0);
4516    let ltree_size = if matches!(options.format, WriteFormat::Ewf1Logical) {
4517        options
4518            .single_files
4519            .as_ref()
4520            .map(ewf1_ltree_payload)
4521            .transpose()?
4522            .as_ref()
4523            .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4524            .transpose()?
4525            .unwrap_or(0)
4526    } else {
4527        0
4528    };
4529    let error2_size = ewf1_error2_payload(&options.acquisition_errors)?
4530        .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4531        .transpose()?
4532        .unwrap_or(0);
4533    let digest_size = digest_payload(&options.hashes)
4534        .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4535        .transpose()?
4536        .unwrap_or(0);
4537    let xhash_size = xhash_payload(&options.hashes, options.compression_values.level)?
4538        .map(|payload| estimated_ewf1_section_size(payload.len() as u64))
4539        .transpose()?
4540        .unwrap_or(0);
4541
4542    checked_sum(
4543        [
4544            ewf1::FILE_HEADER_SIZE as u64,
4545            header_size,
4546            header2_size,
4547            xheader_size,
4548            estimated_ewf1_section_size(volume_data_size(options.format))?,
4549            session_size,
4550            estimated_ewf1_section_size(table_data_size)?,
4551            if writes_table2 {
4552                estimated_ewf1_section_size(table_data_size)?
4553            } else {
4554                0
4555            },
4556            ltree_size,
4557            estimated_ewf1_section_size(chunk_payload_size)?,
4558            error2_size,
4559            digest_size,
4560            xhash_size,
4561            ewf1::SECTION_DESCRIPTOR_SIZE as u64,
4562        ],
4563        "estimated EWF1 segment size",
4564    )
4565}
4566
4567fn estimated_ewf2_segment_size(
4568    chunk_count: usize,
4569    chunk_payload_size: u64,
4570    options: &WriteOptions,
4571    sector_count: u64,
4572    total_chunk_count: u64,
4573) -> Result<u64> {
4574    let device_information_payload =
4575        ewf2_device_information_payload(options, sector_count, total_chunk_count);
4576    let device_information =
4577        ewf2_device_information_section_payload(&device_information_payload, options)?;
4578    let case_data_payload = ewf2_case_data_payload(options, total_chunk_count);
4579    let case_data = ewf2_metadata_section_payload(
4580        &case_data_payload,
4581        options.compression,
4582        options.compression_values.level,
4583    )?;
4584    let error_table = ewf2_error_table_payload(&options.acquisition_errors)?;
4585    let session_table = ewf2_session_table_payload(&options.sessions, &options.tracks)?;
4586    let memory_extents_table = ewf2_memory_extents_table_payload(&options.memory_extents);
4587    let single_files_table_0x21 = ewf2_single_files_aux_u64_table_payload(
4588        &options.ewf2_single_files_tables.table_0x21_entries,
4589    );
4590    let single_files_md5_hash_table =
4591        ewf2_single_files_md5_hash_table_payload(&options.ewf2_single_files_tables.md5_hashes);
4592    let single_files_table_0x23 = ewf2_single_files_aux_u64_table_payload(
4593        &options.ewf2_single_files_tables.table_0x23_entries,
4594    );
4595    let increment_data_size =
4596        options
4597            .ewf2_increment_data
4598            .iter()
4599            .try_fold(0_u64, |total, payload| -> Result<u64> {
4600                let section_size = estimated_ewf2_section_size(
4601                    u64::try_from(payload.len()).expect("usize fits u64"),
4602                )?;
4603                total.checked_add(section_size).ok_or_else(|| {
4604                    EwfError::Malformed("writer estimated EWF2 increment data size overflow".into())
4605                })
4606            })?;
4607    let md5_hash = options.hashes.md5.map(ewf2_hash_payload);
4608    let sha1_hash = options.hashes.sha1.map(ewf2_hash_payload);
4609    let final_information_size = options
4610        .ewf2_final_information
4611        .as_ref()
4612        .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4613        .transpose()?
4614        .unwrap_or(0);
4615    let analytical_data = options
4616        .ewf2_analytical_data
4617        .as_deref()
4618        .map(|data| {
4619            ewf2_string_section_payload(data, options.compression, options.compression_values.level)
4620        })
4621        .transpose()?;
4622    let restart_data = options
4623        .ewf2_restart_data
4624        .as_deref()
4625        .map(|data| {
4626            ewf2_string_section_payload(data, options.compression, options.compression_values.level)
4627        })
4628        .transpose()?;
4629    let table_entry_bytes = u64::try_from(chunk_count)
4630        .map_err(|_| EwfError::Malformed("writer segment chunk count does not fit u64".into()))?
4631        .checked_mul(ewf2::TABLE_ENTRY_SIZE as u64)
4632        .ok_or_else(|| EwfError::Malformed("writer EWF2 table entry size overflow".into()))?;
4633    let table_data_size = (EWF2_TABLE_HEADER_V2_SIZE as u64)
4634        .checked_add(table_entry_bytes)
4635        .and_then(|value| value.checked_add(EWF2_TABLE_FOOTER_SIZE as u64))
4636        .ok_or_else(|| EwfError::Malformed("writer EWF2 table size overflow".into()))?;
4637
4638    checked_sum(
4639        [
4640            ewf2::FILE_HEADER_SIZE as u64,
4641            estimated_ewf2_section_size(device_information.len() as u64)?,
4642            estimated_ewf2_section_size(case_data.len() as u64)?,
4643            error_table
4644                .as_ref()
4645                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4646                .transpose()?
4647                .unwrap_or(0),
4648            session_table
4649                .as_ref()
4650                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4651                .transpose()?
4652                .unwrap_or(0),
4653            memory_extents_table
4654                .as_ref()
4655                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4656                .transpose()?
4657                .unwrap_or(0),
4658            increment_data_size,
4659            single_files_table_0x21
4660                .as_ref()
4661                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4662                .transpose()?
4663                .unwrap_or(0),
4664            single_files_md5_hash_table
4665                .as_ref()
4666                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4667                .transpose()?
4668                .unwrap_or(0),
4669            single_files_table_0x23
4670                .as_ref()
4671                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4672                .transpose()?
4673                .unwrap_or(0),
4674            estimated_ewf2_section_size(table_data_size)?,
4675            estimated_ewf2_section_size(chunk_payload_size)?,
4676            md5_hash
4677                .as_ref()
4678                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4679                .transpose()?
4680                .unwrap_or(0),
4681            sha1_hash
4682                .as_ref()
4683                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4684                .transpose()?
4685                .unwrap_or(0),
4686            final_information_size,
4687            analytical_data
4688                .as_ref()
4689                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4690                .transpose()?
4691                .unwrap_or(0),
4692            restart_data
4693                .as_ref()
4694                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4695                .transpose()?
4696                .unwrap_or(0),
4697            options
4698                .single_files
4699                .as_ref()
4700                .map(ewf2_single_files_data_payload)
4701                .transpose()?
4702                .as_ref()
4703                .map(|payload| estimated_ewf2_section_size(payload.len() as u64))
4704                .transpose()?
4705                .unwrap_or(0),
4706            ewf2::SECTION_DESCRIPTOR_SIZE as u64,
4707        ],
4708        "estimated EWF2 segment size",
4709    )
4710}
4711
4712fn estimated_ewf1_section_size(data_size: u64) -> Result<u64> {
4713    (ewf1::SECTION_DESCRIPTOR_SIZE as u64)
4714        .checked_add(data_size)
4715        .ok_or_else(|| EwfError::Malformed("writer estimated EWF1 section size overflow".into()))
4716}
4717
4718fn estimated_ewf2_section_size(data_size: u64) -> Result<u64> {
4719    (ewf2::SECTION_DESCRIPTOR_SIZE as u64)
4720        .checked_add(data_size)
4721        .ok_or_else(|| EwfError::Malformed("writer estimated EWF2 section size overflow".into()))
4722}
4723
4724fn checked_sum(parts: impl IntoIterator<Item = u64>, label: &str) -> Result<u64> {
4725    parts.into_iter().try_fold(0_u64, |total, part| {
4726        total
4727            .checked_add(part)
4728            .ok_or_else(|| EwfError::Malformed(format!("writer {label} overflow")))
4729    })
4730}
4731
4732fn volume_data_size(format: WriteFormat) -> u64 {
4733    match format {
4734        WriteFormat::Ewf1Physical | WriteFormat::Ewf1Logical => VOLUME_DATA_SIZE as u64,
4735        WriteFormat::Ewf1Smart => 94,
4736        WriteFormat::Ewf2Physical | WriteFormat::Ewf2Logical => {
4737            unreachable!("EWF2 formats do not use EWF1 volume data")
4738        }
4739    }
4740}
4741
4742fn segment_path(first_path: &Path, segment_number: usize) -> Result<PathBuf> {
4743    if segment_number == 1 {
4744        return Ok(first_path.to_path_buf());
4745    }
4746    Ok(first_path.with_extension(v1_segment_extension(first_path, segment_number)?))
4747}
4748
4749fn ewf2_segment_path(first_path: &Path, segment_number: usize) -> Result<PathBuf> {
4750    if segment_number == 1 {
4751        return Ok(first_path.to_path_buf());
4752    }
4753    Ok(first_path.with_extension(v2_segment_extension(first_path, segment_number)?))
4754}
4755
4756fn remove_stale_segment_files(
4757    first_path: &Path,
4758    written_segment_count: usize,
4759    is_v2: bool,
4760) -> Result<()> {
4761    let path_for_segment = if is_v2 {
4762        ewf2_segment_path
4763    } else {
4764        segment_path
4765    };
4766    let mut segment_number = written_segment_count
4767        .checked_add(1)
4768        .ok_or_else(|| EwfError::Malformed("writer stale segment cleanup overflow".into()))?;
4769
4770    loop {
4771        let path = match path_for_segment(first_path, segment_number) {
4772            Ok(path) => path,
4773            Err(EwfError::Unsupported(_)) => return Ok(()),
4774            Err(err) => return Err(err),
4775        };
4776        match fs::remove_file(&path) {
4777            Ok(()) => {
4778                segment_number = segment_number.checked_add(1).ok_or_else(|| {
4779                    EwfError::Malformed("writer stale segment cleanup overflow".into())
4780                })?;
4781            }
4782            Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
4783            Err(err) => return Err(err.into()),
4784        }
4785    }
4786}
4787
4788fn mirror_secondary_segment_files(
4789    primary_base: &Path,
4790    secondary_base: Option<&Path>,
4791    primary_segment_paths: &[PathBuf],
4792    written_segment_count: usize,
4793    is_v2: bool,
4794) -> Result<Vec<PathBuf>> {
4795    let Some(secondary_base) = secondary_base else {
4796        return Ok(Vec::new());
4797    };
4798    validate_secondary_segment_filename(primary_base, Some(secondary_base))?;
4799
4800    let path_for_segment = if is_v2 {
4801        ewf2_segment_path
4802    } else {
4803        segment_path
4804    };
4805    let mut secondary_segment_paths = Vec::with_capacity(primary_segment_paths.len());
4806    for segment_number in 1..=primary_segment_paths.len() {
4807        let secondary_path = path_for_segment(secondary_base, segment_number)?;
4808        ensure_secondary_segment_path_is_distinct(primary_segment_paths, &secondary_path)?;
4809        secondary_segment_paths.push(secondary_path);
4810    }
4811
4812    for (primary_path, secondary_path) in primary_segment_paths
4813        .iter()
4814        .zip(secondary_segment_paths.iter())
4815    {
4816        fs::copy(primary_path, secondary_path)?;
4817    }
4818    remove_stale_segment_files(secondary_base, written_segment_count, is_v2)?;
4819
4820    Ok(secondary_segment_paths)
4821}
4822
4823fn validate_secondary_segment_filename(
4824    primary_base: &Path,
4825    secondary_base: Option<&Path>,
4826) -> Result<()> {
4827    let Some(secondary_base) = secondary_base else {
4828        return Ok(());
4829    };
4830    if normalized_output_path(primary_base)? == normalized_output_path(secondary_base)? {
4831        return Err(EwfError::Unsupported(
4832            "secondary segment filename overlaps primary output".into(),
4833        ));
4834    }
4835    Ok(())
4836}
4837
4838fn ensure_secondary_segment_path_is_distinct(
4839    primary_segment_paths: &[PathBuf],
4840    secondary_path: &Path,
4841) -> Result<()> {
4842    let secondary_path = normalized_output_path(secondary_path)?;
4843    for primary_path in primary_segment_paths {
4844        if normalized_output_path(primary_path)? == secondary_path {
4845            return Err(EwfError::Unsupported(
4846                "secondary segment filename overlaps primary output".into(),
4847            ));
4848        }
4849    }
4850    Ok(())
4851}
4852
4853fn normalized_output_path(path: &Path) -> Result<PathBuf> {
4854    let absolute = if path.is_absolute() {
4855        path.to_path_buf()
4856    } else {
4857        std::env::current_dir()?.join(path)
4858    };
4859    let mut normalized = PathBuf::new();
4860    for component in absolute.components() {
4861        match component {
4862            Component::CurDir => {}
4863            Component::ParentDir => {
4864                normalized.pop();
4865            }
4866            other => normalized.push(other.as_os_str()),
4867        }
4868    }
4869    Ok(normalized)
4870}
4871
4872fn v1_segment_extension(first_path: &Path, segment_number: usize) -> Result<String> {
4873    let prefix = first_path
4874        .extension()
4875        .and_then(|value| value.to_str())
4876        .and_then(|value| value.chars().next())
4877        .unwrap_or('E')
4878        .to_ascii_uppercase();
4879    if segment_number <= 99 {
4880        return Ok(format!("{prefix}{segment_number:02}"));
4881    }
4882
4883    let alpha_index = segment_number - 100;
4884    let prefix_offset = alpha_index / (26 * 26);
4885    let prefix = char::from_u32(
4886        (prefix as u32)
4887            .checked_add(prefix_offset as u32)
4888            .ok_or_else(|| {
4889                EwfError::Unsupported("EWF1 writer segment extension prefix overflow".into())
4890            })?,
4891    )
4892    .ok_or_else(|| EwfError::Unsupported("EWF1 writer segment extension prefix overflow".into()))?;
4893    if !prefix.is_ascii_uppercase() {
4894        return Err(EwfError::Unsupported(
4895            "EWF1 writer segment count exceeds supported extensions".into(),
4896        ));
4897    }
4898
4899    let suffix = alpha_index % (26 * 26);
4900    let first = char::from(b'A' + u8::try_from(suffix / 26).expect("suffix fits u8"));
4901    let second = char::from(b'A' + u8::try_from(suffix % 26).expect("suffix fits u8"));
4902    Ok(format!("{prefix}{first}{second}"))
4903}
4904
4905fn v2_segment_extension(first_path: &Path, segment_number: usize) -> Result<String> {
4906    let prefix = first_path
4907        .extension()
4908        .and_then(|value| value.to_str())
4909        .and_then(|value| value.chars().next())
4910        .unwrap_or('E')
4911        .to_ascii_uppercase();
4912    if segment_number <= 99 {
4913        return Ok(format!("{prefix}x{segment_number:02}"));
4914    }
4915
4916    let alpha_index = segment_number - 100;
4917    let infix_offset = alpha_index / (26 * 26);
4918    let infix = char::from_u32(u32::from(b'x') + infix_offset as u32)
4919        .ok_or_else(|| EwfError::Unsupported("EWF2 writer segment extension overflow".into()))?;
4920    if !matches!(infix, 'x'..='z') {
4921        return Err(EwfError::Unsupported(
4922            "EWF2 writer segment count exceeds supported extensions".into(),
4923        ));
4924    }
4925
4926    let suffix = alpha_index % (26 * 26);
4927    let first = char::from(b'A' + u8::try_from(suffix / 26).expect("suffix fits u8"));
4928    let second = char::from(b'A' + u8::try_from(suffix % 26).expect("suffix fits u8"));
4929    Ok(format!("{prefix}{infix}{first}{second}"))
4930}
4931
4932fn volume_data(options: &WriteOptions, chunk_count: u32, sector_count: u64) -> Result<Vec<u8>> {
4933    let mut volume = vec![0; usize::try_from(volume_data_size(options.format)).expect("fits")];
4934    if options.format == WriteFormat::Ewf1Smart {
4935        volume[0] = 1;
4936    } else {
4937        volume[0] = ewf1_media_type_value(options.format, options.media_profile.media_type);
4938        volume[36] = ewf1_media_flags_value(options.format, options.media_profile);
4939    }
4940    volume[4..8].copy_from_slice(&chunk_count.to_le_bytes());
4941    volume[8..12].copy_from_slice(&options.sectors_per_chunk.to_le_bytes());
4942    volume[12..16].copy_from_slice(&options.bytes_per_sector.to_le_bytes());
4943    volume[52] = ewf1_compression_level_value(options.compression_values.level);
4944    if options.format == WriteFormat::Ewf1Smart {
4945        let sector_count = u32::try_from(sector_count)
4946            .map_err(|_| EwfError::Unsupported("EWF-S01 writer sector count exceeds u32".into()))?;
4947        volume[16..20].copy_from_slice(&sector_count.to_le_bytes());
4948        volume[85..90].copy_from_slice(b"SMART");
4949    } else {
4950        volume[16..24].copy_from_slice(&sector_count.to_le_bytes());
4951    }
4952    if let Some(identifier) = options
4953        .set_identifier
4954        .filter(|_| options.format != WriteFormat::Ewf1Smart)
4955    {
4956        volume[64..80].copy_from_slice(&identifier);
4957    }
4958    if let Some(error_granularity) = options
4959        .media_profile
4960        .error_granularity
4961        .filter(|_| options.format != WriteFormat::Ewf1Smart)
4962    {
4963        let error_granularity = u32::try_from(error_granularity).map_err(|_| {
4964            EwfError::Unsupported("EWF1 writer error granularity exceeds u32".into())
4965        })?;
4966        volume[56..60].copy_from_slice(&error_granularity.to_le_bytes());
4967    }
4968    let checksum_offset = volume.len() - 4;
4969    let checksum = adler32(&volume[..checksum_offset]);
4970    volume[checksum_offset..].copy_from_slice(&checksum.to_le_bytes());
4971    Ok(volume)
4972}
4973
4974fn ewf1_compression_level_value(level: WriteCompressionLevel) -> u8 {
4975    match level {
4976        WriteCompressionLevel::Default | WriteCompressionLevel::None => 0,
4977        WriteCompressionLevel::Fast => 1,
4978        WriteCompressionLevel::Best => 2,
4979    }
4980}
4981
4982fn ewf1_media_type_value(format: WriteFormat, media_type: Option<MediaType>) -> u8 {
4983    media_type.map_or_else(
4984        || match format {
4985            WriteFormat::Ewf1Logical => 0x0e,
4986            WriteFormat::Ewf1Physical | WriteFormat::Ewf1Smart => 0x00,
4987            WriteFormat::Ewf2Physical | WriteFormat::Ewf2Logical => {
4988                unreachable!("EWF2 formats do not use EWF1 media type values")
4989            }
4990        },
4991        ewf1_media_type_byte,
4992    )
4993}
4994
4995fn ewf1_media_type_byte(media_type: MediaType) -> u8 {
4996    match media_type {
4997        MediaType::Removable => 0x00,
4998        MediaType::Fixed => 0x01,
4999        MediaType::Optical => 0x03,
5000        MediaType::SingleFiles => 0x0e,
5001        MediaType::Memory => 0x10,
5002        MediaType::Unknown(value) => value,
5003    }
5004}
5005
5006fn ewf1_media_flags_value(format: WriteFormat, profile: WriteMediaProfile) -> u8 {
5007    let mut flags = 0x01;
5008    if format == WriteFormat::Ewf1Physical {
5009        flags |= 0x02;
5010    }
5011    if profile.fastbloc {
5012        flags |= 0x04;
5013    }
5014    if profile.tableau {
5015        flags |= 0x08;
5016    }
5017    flags
5018}
5019
5020fn table_header(chunk_count: u32, sectors_data_offset: u64) -> [u8; 24] {
5021    let mut table = [0; 24];
5022    table[0..4].copy_from_slice(&chunk_count.to_le_bytes());
5023    table[8..16].copy_from_slice(&sectors_data_offset.to_le_bytes());
5024    let checksum = adler32(&table[..20]);
5025    table[20..24].copy_from_slice(&checksum.to_le_bytes());
5026    table
5027}
5028
5029fn append_ewf1_table_data(
5030    bytes: &mut Vec<u8>,
5031    chunk_count: u32,
5032    sectors_data_offset: u64,
5033    chunks: &[ChunkDescriptor],
5034    include_footer: bool,
5035) -> Result<()> {
5036    bytes.extend_from_slice(&table_header(chunk_count, sectors_data_offset));
5037    let table_entries_start = bytes.len();
5038    let mut relative_offset = 0_u64;
5039    for chunk in chunks {
5040        let mut entry = u32::try_from(relative_offset).map_err(|_| {
5041            EwfError::Unsupported("EWF1 writer raw chunk offset exceeds u32".into())
5042        })?;
5043        if entry & 0x8000_0000 != 0 {
5044            return Err(EwfError::Unsupported(
5045                "EWF1 writer raw chunk offset exceeds 31 bits".into(),
5046            ));
5047        }
5048        if chunk.compressed {
5049            entry |= 0x8000_0000;
5050        }
5051        bytes.extend_from_slice(&entry.to_le_bytes());
5052        relative_offset = relative_offset
5053            .checked_add(chunk.data_size)
5054            .ok_or_else(|| EwfError::Malformed("writer chunk offset overflow".into()))?;
5055    }
5056    if include_footer {
5057        let entries_checksum = adler32(&bytes[table_entries_start..]);
5058        bytes.extend_from_slice(&entries_checksum.to_le_bytes());
5059    }
5060    Ok(())
5061}
5062
5063fn section_desc(section_type: &[u8], next: u64, size: u64) -> [u8; ewf1::SECTION_DESCRIPTOR_SIZE] {
5064    let mut desc = [0; ewf1::SECTION_DESCRIPTOR_SIZE];
5065    desc[..section_type.len()].copy_from_slice(section_type);
5066    desc[16..24].copy_from_slice(&next.to_le_bytes());
5067    desc[24..32].copy_from_slice(&size.to_le_bytes());
5068    let checksum = adler32(&desc[..ewf1::SECTION_DESCRIPTOR_SIZE - 4]);
5069    desc[ewf1::SECTION_DESCRIPTOR_SIZE - 4..].copy_from_slice(&checksum.to_le_bytes());
5070    desc
5071}
5072
5073fn header_payload(
5074    metadata: &EwfMetadata,
5075    header_codepage: HeaderCodepage,
5076    compression_level: WriteCompressionLevel,
5077) -> Result<Option<Vec<u8>>> {
5078    ewf1_header_text(metadata, Ewf1HeaderDateStyle::Header)
5079        .map(|text| {
5080            zlib_payload(
5081                &encode_header_text(&text, header_codepage),
5082                compression_level,
5083            )
5084        })
5085        .transpose()
5086}
5087
5088fn header2_payload(
5089    metadata: &EwfMetadata,
5090    compression_level: WriteCompressionLevel,
5091) -> Result<Option<Vec<u8>>> {
5092    ewf1_header_text(metadata, Ewf1HeaderDateStyle::Header2)
5093        .map(|text| zlib_payload(&utf16le(&text), compression_level))
5094        .transpose()
5095}
5096
5097fn xheader_payload(
5098    metadata: &EwfMetadata,
5099    compression_level: WriteCompressionLevel,
5100) -> Result<Option<Vec<u8>>> {
5101    let fields = [
5102        ("case_number", metadata.case_number.as_deref()),
5103        ("description", metadata.description.as_deref()),
5104        ("examiner_name", metadata.examiner.as_deref()),
5105        ("evidence_number", metadata.evidence_number.as_deref()),
5106        ("notes", metadata.notes.as_deref()),
5107        ("acquiry_operating_system", metadata.os_version.as_deref()),
5108        ("acquiry_date", metadata.acquisition_date.as_deref()),
5109        ("acquiry_software", metadata.acquisition_software.as_deref()),
5110        (
5111            "acquiry_software_version",
5112            metadata.acquisition_software_version.as_deref(),
5113        ),
5114        ("password", metadata.password.as_deref()),
5115    ];
5116    if fields.iter().all(|(_, value)| value.is_none()) && metadata.header_values.is_empty() {
5117        return Ok(None);
5118    }
5119
5120    let mut xml = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xheader>\n");
5121    for (tag, value) in fields {
5122        let Some(value) = value else {
5123            continue;
5124        };
5125        if value.is_empty() {
5126            continue;
5127        }
5128        xml.push('\t');
5129        xml.push('<');
5130        xml.push_str(tag);
5131        xml.push('>');
5132        xml.push_str(&escape_xml_text(&xheader_field_value(tag, value)));
5133        xml.push_str("</");
5134        xml.push_str(tag);
5135        xml.push_str(">\n");
5136    }
5137    for (tag, value) in &metadata.header_values {
5138        if fields.iter().any(|(existing_tag, _)| existing_tag == tag) || value.is_empty() {
5139            continue;
5140        }
5141        xml.push('\t');
5142        xml.push('<');
5143        xml.push_str(tag);
5144        xml.push('>');
5145        xml.push_str(&escape_xml_text(&xheader_field_value(tag, value)));
5146        xml.push_str("</");
5147        xml.push_str(tag);
5148        xml.push_str(">\n");
5149    }
5150    xml.push_str("</xheader>\n\n");
5151
5152    let mut payload = vec![0xef, 0xbb, 0xbf];
5153    payload.extend_from_slice(xml.as_bytes());
5154    Ok(Some(zlib_payload(&payload, compression_level)?))
5155}
5156
5157fn xheader_field_value<'a>(tag: &str, value: &'a str) -> Cow<'a, str> {
5158    if tag == "acquiry_date" {
5159        format_xheader_date_value(value)
5160    } else {
5161        Cow::Borrowed(value)
5162    }
5163}
5164
5165fn zlib_payload(payload: &[u8], compression_level: WriteCompressionLevel) -> Result<Vec<u8>> {
5166    let mut encoder = ZlibEncoder::new(Vec::new(), zlib_compression(compression_level));
5167    encoder.write_all(payload)?;
5168    Ok(encoder.finish()?)
5169}
5170
5171fn escape_xml_text(value: &str) -> String {
5172    let mut escaped = String::with_capacity(value.len());
5173    for ch in value.chars() {
5174        match ch {
5175            '&' => escaped.push_str("&amp;"),
5176            '<' => escaped.push_str("&lt;"),
5177            '>' => escaped.push_str("&gt;"),
5178            '"' => escaped.push_str("&quot;"),
5179            '\'' => escaped.push_str("&apos;"),
5180            _ => escaped.push(ch),
5181        }
5182    }
5183    escaped
5184}
5185
5186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5187enum Ewf1HeaderDateStyle {
5188    Header,
5189    Header2,
5190}
5191
5192fn ewf1_header_text(metadata: &EwfMetadata, date_style: Ewf1HeaderDateStyle) -> Option<String> {
5193    let fields = [
5194        ("c", metadata.case_number.as_deref()),
5195        ("n", metadata.evidence_number.as_deref()),
5196        ("a", metadata.description.as_deref()),
5197        ("e", metadata.examiner.as_deref()),
5198        ("t", metadata.notes.as_deref()),
5199        ("av", metadata.acquisition_software_version.as_deref()),
5200        ("ov", metadata.os_version.as_deref()),
5201        ("m", metadata.acquisition_date.as_deref()),
5202        ("u", metadata.system_date.as_deref()),
5203        ("p", metadata.password.as_deref()),
5204    ];
5205    if fields.iter().all(|(_, value)| value.is_none()) && metadata.header_values.is_empty() {
5206        return None;
5207    }
5208
5209    let mut names = Vec::new();
5210    let mut values = Vec::new();
5211    for (name, value) in fields {
5212        if let Some(value) = value {
5213            names.push(name.to_string());
5214            values.push(sanitize_header_value(&ewf1_header_field_value(
5215                name, value, date_style,
5216            )));
5217        }
5218    }
5219    for (name, value) in &metadata.header_values {
5220        let tag = ewf1_header_tag(name);
5221        if metadata.password.is_some() && tag == "p" {
5222            continue;
5223        }
5224        if names.iter().any(|existing| existing == tag) {
5225            continue;
5226        }
5227        names.push(tag.to_string());
5228        values.push(sanitize_header_value(&ewf1_header_field_value(
5229            tag, value, date_style,
5230        )));
5231    }
5232
5233    let text = format!("1\nmain\n{}\n{}\n", names.join("\t"), values.join("\t"));
5234    Some(text)
5235}
5236
5237fn ewf1_header_tag(identifier: &str) -> &str {
5238    match identifier {
5239        "acquiry_date" => "m",
5240        "acquiry_operating_system" => "ov",
5241        "acquiry_software_version" => "av",
5242        "case_number" => "c",
5243        "compression_level" => "r",
5244        "description" => "a",
5245        "device_label" => "l",
5246        "evidence_number" => "n",
5247        "examiner_name" => "e",
5248        "extents" => "ext",
5249        "model" => "md",
5250        "notes" => "t",
5251        "password" => "p",
5252        "process_identifier" => "pid",
5253        "serial_number" => "sn",
5254        "system_date" => "u",
5255        "unknown_dc" => "dc",
5256        _ => identifier,
5257    }
5258}
5259
5260fn ewf1_header_field_value<'a>(
5261    name: &str,
5262    value: &'a str,
5263    date_style: Ewf1HeaderDateStyle,
5264) -> Cow<'a, str> {
5265    if !matches!(name, "m" | "u" | "acquiry_date" | "system_date") {
5266        return Cow::Borrowed(value);
5267    }
5268
5269    match date_style {
5270        Ewf1HeaderDateStyle::Header => format_ewf1_header_date_value(value),
5271        Ewf1HeaderDateStyle::Header2 => format_ewf1_header2_date_value(value),
5272    }
5273}
5274
5275fn sanitize_header_value(value: &str) -> String {
5276    value
5277        .chars()
5278        .map(|ch| {
5279            if matches!(ch, '\t' | '\n' | '\r') {
5280                ' '
5281            } else {
5282                ch
5283            }
5284        })
5285        .collect()
5286}
5287
5288fn digest_payload(hashes: &WriteHashes) -> Option<[u8; 80]> {
5289    if hashes.md5.is_none() && hashes.sha1.is_none() {
5290        return None;
5291    }
5292
5293    let mut digest = [0; 80];
5294    if let Some(md5) = hashes.md5 {
5295        digest[..16].copy_from_slice(&md5);
5296    }
5297    if let Some(sha1) = hashes.sha1 {
5298        digest[16..36].copy_from_slice(&sha1);
5299    }
5300    let checksum = adler32(&digest[..76]);
5301    digest[76..80].copy_from_slice(&checksum.to_le_bytes());
5302    Some(digest)
5303}
5304
5305fn xhash_payload(
5306    hashes: &WriteHashes,
5307    compression_level: WriteCompressionLevel,
5308) -> Result<Option<Vec<u8>>> {
5309    if hashes.md5.is_none() && hashes.sha1.is_none() && hashes.hash_values.is_empty() {
5310        return Ok(None);
5311    }
5312
5313    let mut xml = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xhash>\n");
5314    if let Some(md5) = hashes.md5 {
5315        xml.push_str("\t<md5>");
5316        xml.push_str(&hex_string(&md5));
5317        xml.push_str("</md5>\n");
5318    }
5319    if let Some(sha1) = hashes.sha1 {
5320        xml.push_str("\t<sha1>");
5321        xml.push_str(&hex_string(&sha1));
5322        xml.push_str("</sha1>\n");
5323    }
5324    for (tag, value) in &hashes.hash_values {
5325        if matches!(tag.as_str(), "MD5" | "md5" | "SHA1" | "sha1") || value.is_empty() {
5326            continue;
5327        }
5328        xml.push('\t');
5329        xml.push('<');
5330        xml.push_str(tag);
5331        xml.push('>');
5332        xml.push_str(&escape_xml_text(value));
5333        xml.push_str("</");
5334        xml.push_str(tag);
5335        xml.push_str(">\n");
5336    }
5337    xml.push_str("</xhash>\n\n");
5338
5339    let mut payload = vec![0xef, 0xbb, 0xbf];
5340    payload.extend_from_slice(xml.as_bytes());
5341    Ok(Some(zlib_payload(&payload, compression_level)?))
5342}
5343
5344fn hex_string(bytes: &[u8]) -> String {
5345    const HEX: &[u8; 16] = b"0123456789abcdef";
5346    let mut out = String::with_capacity(bytes.len() * 2);
5347    for byte in bytes {
5348        out.push(char::from(HEX[(byte >> 4) as usize]));
5349        out.push(char::from(HEX[(byte & 0x0f) as usize]));
5350    }
5351    out
5352}