ewf_image/
logical_verify.rs1use std::io::Write;
4use std::ops::ControlFlow;
5
6use md5::{Digest, Md5};
7use sha1::Sha1;
8use sha2::Sha256;
9
10use crate::{
11 ComputedHashes, EwfError, HashAlgorithm, HashComparison, HashReference, Image, Result,
12 SingleFileEntry, SingleFileEntryType,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize))]
18#[non_exhaustive]
19pub struct SingleFileProgress {
20 pub bytes_processed: u64,
22 pub bytes_total: u64,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize))]
29#[non_exhaustive]
30pub struct SingleFileVerification {
31 pub hashes: ComputedHashes,
33 pub comparisons: Vec<HashComparison>,
35 pub bytes_verified: u64,
37}
38
39impl SingleFileVerification {
40 pub fn references_match(&self) -> Option<bool> {
42 (!self.comparisons.is_empty()).then(|| self.comparisons.iter().all(|c| c.matches))
43 }
44}
45
46impl Image {
47 pub fn verify_single_file(&self, entry: &SingleFileEntry) -> Result<SingleFileVerification> {
51 self.verify_single_file_with_progress(entry, |_| ControlFlow::Continue(()))
52 }
53
54 pub fn verify_single_file_with_progress(
56 &self,
57 entry: &SingleFileEntry,
58 progress: impl FnMut(SingleFileProgress) -> ControlFlow<()>,
59 ) -> Result<SingleFileVerification> {
60 self.copy_single_file_with_progress(entry, &mut std::io::sink(), progress)
61 }
62
63 pub fn copy_single_file_with_progress(
69 &self,
70 entry: &SingleFileEntry,
71 output: &mut impl Write,
72 mut progress: impl FnMut(SingleFileProgress) -> ControlFlow<()>,
73 ) -> Result<SingleFileVerification> {
74 if entry.entry_type() != Some(SingleFileEntryType::File) {
75 return Err(EwfError::Unsupported(
76 "entry is not a regular logical file".into(),
77 ));
78 }
79 self.read_single_file_at_strict(entry, &mut [], 0)?;
81 let references = [
82 (HashAlgorithm::Md5, "MD5", entry.md5.as_deref()),
83 (HashAlgorithm::Sha1, "SHA1", entry.sha1.as_deref()),
84 ];
85 for (_, name, value) in references {
86 if let Some(value) = value {
87 crate::hashes::validate_digest(name, value)?;
88 }
89 }
90 let total = crate::image::single_file_size(entry)?;
91 let mut offset = 0;
92 let mut buffer = vec![0; 1024 * 1024];
93 let mut md5 = Md5::new();
94 let mut sha1 = Sha1::new();
95 let mut sha256 = Sha256::new();
96 loop {
97 if progress(SingleFileProgress {
98 bytes_processed: offset,
99 bytes_total: total,
100 })
101 .is_break()
102 {
103 return Err(EwfError::Aborted);
104 }
105 if offset == total {
106 break;
107 }
108 let length = (total - offset).min(buffer.len() as u64) as usize;
109 let read = self.read_single_file_at_strict(entry, &mut buffer[..length], offset)?;
110 if read != length {
111 return Err(EwfError::Malformed(
112 "logical file read was truncated".into(),
113 ));
114 }
115 output.write_all(&buffer[..read])?;
116 md5.update(&buffer[..read]);
117 sha1.update(&buffer[..read]);
118 sha256.update(&buffer[..read]);
119 offset += read as u64;
120 }
121 let hashes = ComputedHashes {
122 md5: md5.finalize().into(),
123 sha1: sha1.finalize().into(),
124 sha256: sha256.finalize().into(),
125 };
126 let mut comparisons = Vec::new();
127 if let Some(value) = &entry.md5 {
128 let expected = crate::types::parse_hex_array::<16>(value).expect("validated digest");
129 comparisons.push(HashComparison {
130 algorithm: HashAlgorithm::Md5,
131 reference: HashReference::Stored,
132 expected: expected.to_vec(),
133 computed: hashes.md5.to_vec(),
134 matches: expected == hashes.md5,
135 });
136 }
137 if let Some(value) = &entry.sha1 {
138 let expected = crate::types::parse_hex_array::<20>(value).expect("validated digest");
139 comparisons.push(HashComparison {
140 algorithm: HashAlgorithm::Sha1,
141 reference: HashReference::Stored,
142 expected: expected.to_vec(),
143 computed: hashes.sha1.to_vec(),
144 matches: expected == hashes.sha1,
145 });
146 }
147 Ok(SingleFileVerification {
148 hashes,
149 comparisons,
150 bytes_verified: offset,
151 })
152 }
153}