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