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