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)]
75pub struct WriteOptions {
77 pub format: WriteFormat,
79 pub sectors_per_chunk: u32,
81 pub bytes_per_sector: u32,
83 pub set_identifier: Option<[u8; 16]>,
85 pub compression: WriteCompression,
87 pub compression_values: WriteCompressionValues,
89 pub hashes: WriteHashes,
91 pub maximum_segment_size: Option<u64>,
93 pub secondary_segment_filename: Option<PathBuf>,
95 pub media_size: Option<u64>,
97 pub metadata: EwfMetadata,
99 pub header_codepage: HeaderCodepage,
101 pub header_values_date_format: HeaderDateFormat,
103 pub acquisition_errors: Vec<AcquisitionError>,
105 pub checksum_errors: Vec<SectorRange>,
107 pub sessions: Vec<SectorRange>,
109 pub tracks: Vec<SectorRange>,
111 pub media_profile: WriteMediaProfile,
113 pub memory_extents: Vec<MemoryExtent>,
115 pub single_files: Option<SingleFilesInfo>,
117 pub ewf2_single_files_tables: SingleFilesAuxTables,
119 pub ewf2_increment_data: Vec<Vec<u8>>,
121 pub ewf2_final_information: Option<Vec<u8>>,
123 pub ewf2_restart_data: Option<String>,
125 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 pub fn copy_media_values_from_image(&mut self, image: &Image) -> Result<()> {
169 self.copy_media_values_from_info(image.info())
170 }
171
172 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 pub fn copy_header_values_from_image(&mut self, image: &Image) {
216 self.copy_header_values_from_info(image.info());
217 }
218
219 pub fn copy_header_values_from_info(&mut self, info: &ImageInfo) {
221 self.copy_header_values_from_metadata(&info.metadata);
222 }
223
224 pub fn copy_header_values_from_metadata(&mut self, metadata: &EwfMetadata) {
226 self.metadata.copy_header_values_from(metadata);
227 }
228
229 pub fn copy_hash_values_from_image(&mut self, image: &Image) {
231 self.copy_hash_values_from_info(image.info());
232 }
233
234 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 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)]
246pub enum WriteFormat {
248 #[default]
250 Ewf1Physical,
251 Ewf1Logical,
253 Ewf1Smart,
255 Ewf2Physical,
257 Ewf2Logical,
259}
260
261#[derive(Debug, Clone, Default, PartialEq, Eq)]
262pub struct WriteHashes {
264 pub md5: Option<[u8; 16]>,
266 pub sha1: Option<[u8; 20]>,
268 pub hash_values: BTreeMap<String, String>,
270}
271
272impl WriteHashes {
273 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 pub fn hash_value(&self, identifier: &str) -> Option<&str> {
282 self.hash_values.get(identifier).map(String::as_str)
283 }
284
285 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 pub fn number_of_hash_values(&self) -> usize {
310 self.hash_values.len()
311 }
312
313 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)]
345pub struct WriteMediaProfile {
347 pub media_type: Option<MediaType>,
349 pub error_granularity: Option<u64>,
351 pub fastbloc: bool,
353 pub tableau: bool,
355}
356
357#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
358pub enum WriteCompression {
360 #[default]
362 None,
363 Zlib,
365 Bzip2,
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub struct WriteCompressionValues {
372 pub level: WriteCompressionLevel,
374 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)]
388pub enum WriteCompressionLevel {
390 #[default]
392 Default,
393 None,
395 Fast,
397 Best,
399}
400
401#[derive(Debug, Clone, PartialEq, Eq)]
402pub struct WriteResult {
404 pub segment_paths: Vec<PathBuf>,
406 pub secondary_segment_paths: Vec<PathBuf>,
408 pub logical_size: u64,
410 pub chunk_size: u64,
412 pub chunk_count: u64,
414}
415
416pub 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 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 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 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 pub fn filename(&self) -> &Path {
546 self.path.as_path()
547 }
548
549 pub fn segment_filename(&self) -> &Path {
551 self.path.as_path()
552 }
553
554 pub fn set_segment_filename(&mut self, path: impl AsRef<Path>) {
556 self.path = path.as_ref().to_path_buf();
557 }
558
559 pub fn secondary_segment_filename(&self) -> Option<&Path> {
561 self.options.secondary_segment_filename.as_deref()
562 }
563
564 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 pub fn clear_secondary_segment_filename(&mut self) {
579 self.options.secondary_segment_filename = None;
580 }
581
582 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 pub fn write_buffer(&mut self, data: &[u8]) -> Result<usize> {
601 self.ensure_not_aborted()?;
602 self.write_to_current(data)
603 }
604
605 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 pub fn write_buffer_at_offset(&mut self, data: &[u8], offset: u64) -> Result<usize> {
623 self.write_at(data, offset)
624 }
625
626 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 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 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 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 pub fn format(&self) -> WriteFormat {
686 self.options.format
687 }
688
689 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 pub fn position(&self) -> u64 {
706 self.current_offset
707 }
708
709 pub fn chunk_size(&self) -> u64 {
711 self.chunk_size
712 }
713
714 pub fn sectors_per_chunk(&self) -> u32 {
716 self.options.sectors_per_chunk
717 }
718
719 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 pub fn bytes_per_sector(&self) -> u32 {
738 self.options.bytes_per_sector
739 }
740
741 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 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 pub fn media_size(&self) -> Result<u64> {
776 self.logical_size()
777 }
778
779 pub fn number_of_sectors(&self) -> Result<u64> {
785 Ok(self.media_size()? / u64::from(self.options.bytes_per_sector))
786 }
787
788 pub fn maximum_segment_size(&self) -> Option<u64> {
790 self.options.maximum_segment_size
791 }
792
793 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 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 pub fn number_of_chunks_written(&self) -> Result<u64> {
832 Ok(self.logical_size()?.div_ceil(self.chunk_size))
833 }
834
835 pub fn segment_file_set_identifier(&self) -> Option<[u8; 16]> {
837 self.options.set_identifier
838 }
839
840 pub fn signal_abort(&self) {
842 self.abort_signaled.store(true, Ordering::Relaxed);
843 }
844
845 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 pub fn compression_method(&self) -> WriteCompression {
858 self.options.compression
859 }
860
861 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 pub fn compression_values(&self) -> WriteCompressionValues {
880 self.options.compression_values
881 }
882
883 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 pub fn media_type(&self) -> Option<MediaType> {
899 self.options.media_profile.media_type
900 }
901
902 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 pub fn error_granularity(&self) -> Option<u64> {
915 self.options.media_profile.error_granularity
916 }
917
918 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 pub fn media_flags(&self) -> MediaFlags {
931 writer_media_flags(self.options.format, self.options.media_profile)
932 }
933
934 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 pub fn copy_media_values_from_image(&mut self, image: &Image) -> Result<()> {
962 self.copy_media_values_from_info(image.info())
963 }
964
965 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 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 pub fn header_codepage(&self) -> HeaderCodepage {
999 self.options.header_codepage
1000 }
1001
1002 pub fn set_header_codepage(&mut self, header_codepage: HeaderCodepage) {
1004 self.options.header_codepage = header_codepage;
1005 }
1006
1007 pub fn header_values_date_format(&self) -> HeaderDateFormat {
1009 self.options.header_values_date_format
1010 }
1011
1012 pub fn set_header_values_date_format(&mut self, date_format: HeaderDateFormat) {
1014 self.options.header_values_date_format = date_format;
1015 }
1016
1017 pub fn copy_header_values_from_image(&mut self, image: &Image) {
1019 self.copy_header_values_from_info(image.info());
1020 }
1021
1022 pub fn copy_header_values_from_info(&mut self, info: &ImageInfo) {
1024 self.copy_header_values_from_metadata(&info.metadata);
1025 }
1026
1027 pub fn copy_header_values_from_metadata(&mut self, metadata: &EwfMetadata) {
1029 self.options.copy_header_values_from_metadata(metadata);
1030 }
1031
1032 pub fn copy_hash_values_from_image(&mut self, image: &Image) {
1034 self.copy_hash_values_from_info(image.info());
1035 }
1036
1037 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 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 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 pub fn number_of_header_values(&self) -> usize {
1058 self.options.metadata.number_of_header_values()
1059 }
1060
1061 pub fn header_value_identifier(&self, index: usize) -> Option<&str> {
1063 self.options.metadata.header_value_identifier(index)
1064 }
1065
1066 pub fn hash_value(&self, identifier: &str) -> Option<&str> {
1068 self.options.hashes.hash_value(identifier)
1069 }
1070
1071 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 pub fn number_of_hash_values(&self) -> usize {
1087 self.options.hashes.number_of_hash_values()
1088 }
1089
1090 pub fn hash_value_identifier(&self, index: usize) -> Option<&str> {
1092 self.options.hashes.hash_value_identifier(index)
1093 }
1094
1095 pub fn md5_hash(&self) -> Option<[u8; 16]> {
1097 self.options.hashes.md5
1098 }
1099
1100 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 pub fn sha1_hash(&self) -> Option<[u8; 20]> {
1120 self.options.hashes.sha1
1121 }
1122
1123 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 pub fn acquisition_errors(&self) -> &[AcquisitionError] {
1143 &self.options.acquisition_errors
1144 }
1145
1146 pub fn number_of_acquisition_errors(&self) -> usize {
1148 self.options.acquisition_errors.len()
1149 }
1150
1151 pub fn acquisition_error(&self, index: usize) -> Option<&AcquisitionError> {
1153 self.options.acquisition_errors.get(index)
1154 }
1155
1156 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 pub fn checksum_errors(&self) -> &[SectorRange] {
1172 &self.options.checksum_errors
1173 }
1174
1175 pub fn number_of_checksum_errors(&self) -> usize {
1177 self.options.checksum_errors.len()
1178 }
1179
1180 pub fn checksum_error(&self, index: usize) -> Option<&SectorRange> {
1182 self.options.checksum_errors.get(index)
1183 }
1184
1185 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 pub fn sessions(&self) -> &[SectorRange] {
1201 &self.options.sessions
1202 }
1203
1204 pub fn number_of_sessions(&self) -> usize {
1206 self.options.sessions.len()
1207 }
1208
1209 pub fn session(&self, index: usize) -> Option<&SectorRange> {
1211 self.options.sessions.get(index)
1212 }
1213
1214 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 pub fn tracks(&self) -> &[SectorRange] {
1232 &self.options.tracks
1233 }
1234
1235 pub fn number_of_tracks(&self) -> usize {
1237 self.options.tracks.len()
1238 }
1239
1240 pub fn track(&self, index: usize) -> Option<&SectorRange> {
1242 self.options.tracks.get(index)
1243 }
1244
1245 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 pub fn offset(&self) -> u64 {
1263 self.current_offset
1264 }
1265
1266 pub fn seek_offset(&mut self, position: SeekFrom) -> Result<u64> {
1273 self.seek_position(position)
1274 }
1275
1276 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 pub fn finish(self) -> Result<WriteResult> {
1296 self.finish_with_terminal_section(TerminalSection::Done)
1297 }
1298
1299 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 pub fn write_finalize(self) -> Result<WriteResult> {
1448 self.finish()
1449 }
1450
1451 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 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) = <ree {
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(§ion_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(§ion_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(§ion_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(§ion_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(§ion_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(§ion_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(§ion_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(§ion_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) = <ree {
2518 writer.write_all(§ion_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(§ion_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(§ion_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(§ion_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(§ion_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 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(§or_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(§or_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(§ion_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(§or_count.to_le_bytes());
4948 volume[85..90].copy_from_slice(b"SMART");
4949 } else {
4950 volume[16..24].copy_from_slice(§or_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(§ors_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("&"),
5176 '<' => escaped.push_str("<"),
5177 '>' => escaped.push_str(">"),
5178 '"' => escaped.push_str("""),
5179 '\'' => escaped.push_str("'"),
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}