1use flate2::read::ZlibDecoder;
2use md5::{Digest as _, Md5};
3use sha1::{Digest as _, Sha1};
4use std::io::Read as _;
5
6const EVF_SIGNATURE: [u8; 8] = [0x45, 0x56, 0x46, 0x09, 0x0d, 0x0a, 0xff, 0x00];
9const FILE_HEADER_SIZE: usize = 13;
10pub(crate) const SECTION_DESCRIPTOR_SIZE: usize = 76;
11const VOLUME_DATA_MIN: usize = 24;
12
13const KNOWN_TYPES: &[&str] = &[
14 "header", "header2", "volume", "disk", "table", "table2", "sectors", "hash", "digest",
15 "error2", "session", "done", "next", "data", "ltree", "ltreedata",
16];
17
18const EVF2_SIGNATURE: [u8; 8] = [0x45, 0x56, 0x46, 0x32, 0x0d, 0x0a, 0x81, 0x00];
21const LEF2_SIGNATURE: [u8; 8] = [0x4c, 0x45, 0x46, 0x32, 0x0d, 0x0a, 0x81, 0x00];
22const EVF2_FILE_HEADER_SIZE: usize = 32;
23const EVF2_SECTION_DESCRIPTOR_SIZE: usize = 64;
24const EVF2_DATA_FLAG_ENCRYPTED: u32 = 0x0000_0002;
25const EVF2_TYPE_MD5_HASH: u32 = 0x08;
26const EVF2_TYPE_SHA1_HASH: u32 = 0x09;
27const EVF2_TYPE_DONE: u32 = 0x0F;
28const EVF2_TYPE_NEXT: u32 = 0x0D;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum Severity {
34 Info,
35 Warning,
36 Error,
37 Critical,
38}
39
40#[derive(Debug, Clone)]
41pub enum EwfIntegrityAnomaly {
42 InvalidSignature,
44 SegmentNumberZero,
45 SectionDescriptorCrcMismatch {
46 offset: u64,
47 section_type: String,
48 computed: u32,
49 stored: u32,
50 },
51 SectionChainBroken {
52 at_offset: u64,
53 next_offset: u64,
54 },
55 SectionGapNonZero {
56 gap_offset: u64,
57 gap_size: u64,
58 },
59 VolumeSectionMissing,
60 UnknownSectionType {
61 offset: u64,
62 type_name: String,
63 },
64 DoneSectionMissing,
65 ChunkSizeInvalid {
66 sectors_per_chunk: u32,
67 bytes_per_sector: u32,
68 },
69 SectorCountMismatch {
70 declared: u64,
71 expected: u64,
72 },
73 BytesPerSectorInvalid {
74 bytes_per_sector: u32,
75 },
76 TableChunkCountMismatch {
77 in_volume: u32,
78 in_table: u32,
79 },
80 TableEntryOutOfBounds {
81 chunk_index: u32,
82 entry_offset: u64,
83 file_size: u64,
84 },
85 TableEntryOutsideSectorsRange {
86 chunk_index: u32,
87 entry_offset: u64,
88 sectors_start: u64,
89 sectors_end: u64,
90 },
91 SectionGapZero {
92 gap_offset: u64,
93 gap_size: u64,
94 },
95 HashMismatch {
96 computed: [u8; 16],
97 stored: [u8; 16],
98 },
99 HashSectionMissing,
100 SegmentOutOfOrder {
103 segment_number: u16,
104 expected: u16,
105 },
106 DigestSha1Mismatch {
109 computed: [u8; 20],
110 stored: [u8; 20],
111 },
112 ExternalMd5Mismatch {
115 computed: [u8; 16],
116 expected: [u8; 16],
117 },
118 ExternalSha1Mismatch {
120 computed: [u8; 20],
121 expected: [u8; 20],
122 },
123 Ewf2SectionDataHashMismatch {
126 offset: u64,
127 section_type_id: u32,
128 computed: [u8; 16],
129 stored: [u8; 16],
130 },
131 Ewf2EncryptedSection {
133 offset: u64,
134 },
135 Ewf2HashSectionMissing,
137}
138
139impl EwfIntegrityAnomaly {
140 pub fn severity(&self) -> Severity {
141 match self {
142 Self::InvalidSignature => Severity::Critical,
143 Self::SegmentNumberZero => Severity::Error,
144 Self::SectionDescriptorCrcMismatch { .. } => Severity::Error,
145 Self::SectionChainBroken { .. } => Severity::Critical,
146 Self::SectionGapNonZero { .. } => Severity::Warning,
147 Self::VolumeSectionMissing => Severity::Critical,
148 Self::UnknownSectionType { .. } => Severity::Warning,
149 Self::DoneSectionMissing => Severity::Warning,
150 Self::ChunkSizeInvalid { .. } => Severity::Error,
151 Self::SectorCountMismatch { .. } => Severity::Error,
152 Self::BytesPerSectorInvalid { .. } => Severity::Error,
153 Self::TableChunkCountMismatch { .. } => Severity::Error,
154 Self::TableEntryOutOfBounds { .. } => Severity::Error,
155 Self::TableEntryOutsideSectorsRange { .. } => Severity::Error,
156 Self::SectionGapZero { .. } => Severity::Info,
157 Self::HashMismatch { .. } => Severity::Error,
158 Self::HashSectionMissing => Severity::Warning,
159 Self::SegmentOutOfOrder { .. } => Severity::Error,
160 Self::DigestSha1Mismatch { .. } => Severity::Error,
161 Self::ExternalMd5Mismatch { .. } => Severity::Critical,
162 Self::ExternalSha1Mismatch { .. } => Severity::Critical,
163 Self::Ewf2SectionDataHashMismatch { .. } => Severity::Error,
164 Self::Ewf2EncryptedSection { .. } => Severity::Warning,
165 Self::Ewf2HashSectionMissing => Severity::Warning,
166 }
167 }
168}
169
170pub struct EwfIntegrity<'a> {
173 segments: Vec<&'a [u8]>,
174 expected_md5: Option<[u8; 16]>,
175 expected_sha1: Option<[u8; 20]>,
176}
177
178impl<'a> EwfIntegrity<'a> {
179 pub fn new(data: &'a [u8]) -> Self {
181 Self {
182 segments: vec![data],
183 expected_md5: None,
184 expected_sha1: None,
185 }
186 }
187
188 pub fn from_segments(segs: &[&'a [u8]]) -> Self {
190 Self {
191 segments: segs.to_vec(),
192 expected_md5: None,
193 expected_sha1: None,
194 }
195 }
196
197 pub fn with_expected_md5(mut self, hash: [u8; 16]) -> Self {
200 self.expected_md5 = Some(hash);
201 self
202 }
203
204 pub fn with_expected_sha1(mut self, hash: [u8; 20]) -> Self {
207 self.expected_sha1 = Some(hash);
208 self
209 }
210
211 pub fn analyse(&self) -> Vec<EwfIntegrityAnomaly> {
212 let first = self.segments.first().copied().unwrap_or(&[]);
213 if first.len() >= 8
214 && (first[0..8] == EVF2_SIGNATURE || first[0..8] == LEF2_SIGNATURE)
215 {
216 return self.analyse_all_ewf2();
217 }
218 self.analyse_all_ewf1()
219 }
220
221 fn analyse_all_ewf1(&self) -> Vec<EwfIntegrityAnomaly> {
224 let mut issues = Vec::new();
225 let n = self.segments.len();
226 let multi = n > 1;
227 let mut geometry: Option<VolumeGeometry> = None;
228 let mut all_sections: Vec<Vec<Section>> = Vec::with_capacity(n);
229
230 for (idx, &data) in self.segments.iter().enumerate() {
231 let expected_seg_num = (idx + 1) as u16;
232 let is_last = idx == n - 1;
233 let file_size = data.len() as u64;
234
235 if data.len() < FILE_HEADER_SIZE {
236 issues.push(EwfIntegrityAnomaly::SectionChainBroken {
237 at_offset: 0,
238 next_offset: 0,
239 });
240 all_sections.push(Vec::new());
241 continue;
242 }
243
244 if data[0..8] != EVF_SIGNATURE {
245 issues.push(EwfIntegrityAnomaly::InvalidSignature);
246 }
247
248 let seg_num = u16::from_le_bytes(data[9..11].try_into().unwrap());
249 if seg_num == 0 {
250 issues.push(EwfIntegrityAnomaly::SegmentNumberZero);
251 } else if seg_num != expected_seg_num {
252 issues.push(EwfIntegrityAnomaly::SegmentOutOfOrder {
253 segment_number: seg_num,
254 expected: expected_seg_num,
255 });
256 }
257
258 let sections = walk_sections_v1(data, &mut issues);
259
260 if idx == 0 {
262 match sections
263 .iter()
264 .find(|s| s.type_name == "volume" || s.type_name == "disk")
265 {
266 None => issues.push(EwfIntegrityAnomaly::VolumeSectionMissing),
267 Some(v) => geometry = check_volume_v1(data, v.offset, &mut issues),
268 }
269 }
270
271 let vol_count = if !multi && idx == 0 {
273 geometry.as_ref().map(|g| g.chunk_count)
274 } else {
275 None
276 };
277 let sectors_range = sections
278 .iter()
279 .find(|s| s.type_name == "sectors")
280 .map(|s| (s.offset + SECTION_DESCRIPTOR_SIZE as u64, s.offset + s.size));
281 if let Some(table) = sections.iter().find(|s| s.type_name == "table") {
282 check_table_v1(
283 data,
284 table.offset,
285 vol_count,
286 file_size,
287 sectors_range,
288 &mut issues,
289 );
290 }
291
292 if is_last && !sections.iter().any(|s| s.type_name == "done") {
294 issues.push(EwfIntegrityAnomaly::DoneSectionMissing);
295 }
296
297 all_sections.push(sections);
298 }
299
300 if let Some(geom) = &geometry {
302 check_hash_all_segments(
303 &self.segments,
304 &all_sections,
305 geom,
306 self.expected_md5,
307 self.expected_sha1,
308 &mut issues,
309 );
310 }
311
312 issues
313 }
314
315 fn analyse_all_ewf2(&self) -> Vec<EwfIntegrityAnomaly> {
318 let mut issues = Vec::new();
319 let n = self.segments.len();
320
321 for (idx, &data) in self.segments.iter().enumerate() {
322 let expected_seg_num = (idx + 1) as u32;
323
324 if data.len() < EVF2_FILE_HEADER_SIZE {
325 issues.push(EwfIntegrityAnomaly::SectionChainBroken {
326 at_offset: 0,
327 next_offset: 0,
328 });
329 continue;
330 }
331
332 if data[0..8] != EVF2_SIGNATURE && data[0..8] != LEF2_SIGNATURE {
333 issues.push(EwfIntegrityAnomaly::InvalidSignature);
334 }
335
336 let seg_num = u32::from_le_bytes(data[12..16].try_into().unwrap());
337 if seg_num == 0 {
338 issues.push(EwfIntegrityAnomaly::SegmentNumberZero);
339 } else if seg_num != expected_seg_num {
340 issues.push(EwfIntegrityAnomaly::SegmentOutOfOrder {
341 segment_number: seg_num as u16,
342 expected: expected_seg_num as u16,
343 });
344 }
345
346 let mut pos = EVF2_FILE_HEADER_SIZE;
347 let mut has_hash = false;
348
349 loop {
350 if pos + EVF2_SECTION_DESCRIPTOR_SIZE > data.len() {
351 break;
352 }
353 let desc = &data[pos..pos + EVF2_SECTION_DESCRIPTOR_SIZE];
354 let section_type = u32::from_le_bytes(desc[0..4].try_into().unwrap());
355 let data_flags = u32::from_le_bytes(desc[4..8].try_into().unwrap());
356 let data_size = u64::from_le_bytes(desc[16..24].try_into().unwrap()) as usize;
357 let padding_size = u32::from_le_bytes(desc[28..32].try_into().unwrap()) as usize;
358 let stored_hash: [u8; 16] = desc[32..48].try_into().unwrap();
359
360 let body_start = pos + EVF2_SECTION_DESCRIPTOR_SIZE;
361 let body_end = body_start.saturating_add(data_size);
362
363 if data_flags & EVF2_DATA_FLAG_ENCRYPTED != 0 {
364 issues.push(EwfIntegrityAnomaly::Ewf2EncryptedSection {
366 offset: pos as u64,
367 });
368 } else if stored_hash != [0u8; 16] {
369 if let Some(body) = data.get(body_start..body_end) {
371 let computed: [u8; 16] = Md5::digest(body).into();
372 if computed != stored_hash {
373 issues.push(EwfIntegrityAnomaly::Ewf2SectionDataHashMismatch {
374 offset: pos as u64,
375 section_type_id: section_type,
376 computed,
377 stored: stored_hash,
378 });
379 }
380 }
381 }
382
383 if section_type == EVF2_TYPE_MD5_HASH || section_type == EVF2_TYPE_SHA1_HASH {
384 has_hash = true;
385 }
386
387 if section_type == EVF2_TYPE_DONE || section_type == EVF2_TYPE_NEXT {
388 break;
389 }
390
391 let next_pos = body_end.saturating_add(padding_size);
392 if next_pos <= pos {
393 issues.push(EwfIntegrityAnomaly::SectionChainBroken {
394 at_offset: pos as u64,
395 next_offset: next_pos as u64,
396 });
397 break;
398 }
399 pos = next_pos;
400 }
401
402 if idx == n - 1 && !has_hash {
403 issues.push(EwfIntegrityAnomaly::Ewf2HashSectionMissing);
404 }
405 }
406
407 issues
408 }
409}
410
411struct Section {
414 type_name: String,
415 offset: u64,
416 size: u64,
417}
418
419struct VolumeGeometry {
420 chunk_count: u32,
421 sectors_per_chunk: u32,
422 bytes_per_sector: u32,
423 sector_count: u64,
424}
425
426fn walk_sections_v1(data: &[u8], issues: &mut Vec<EwfIntegrityAnomaly>) -> Vec<Section> {
427 let file_size = data.len() as u64;
428 let mut sections = Vec::new();
429 let mut pos = FILE_HEADER_SIZE as u64;
430
431 loop {
432 let off = pos as usize;
433 if off + SECTION_DESCRIPTOR_SIZE > data.len() {
434 break;
435 }
436 let desc = &data[off..off + SECTION_DESCRIPTOR_SIZE];
437
438 let type_end = desc[..16].iter().position(|&b| b == 0).unwrap_or(16);
439 let type_name = String::from_utf8_lossy(&desc[..type_end]).into_owned();
440
441 let stored_crc = u32::from_le_bytes(desc[72..76].try_into().unwrap());
442 let computed_crc = adler32(&desc[..72]);
443 if computed_crc != stored_crc {
444 issues.push(EwfIntegrityAnomaly::SectionDescriptorCrcMismatch {
445 offset: pos,
446 section_type: type_name.clone(),
447 computed: computed_crc,
448 stored: stored_crc,
449 });
450 }
451
452 if !KNOWN_TYPES.contains(&type_name.as_str()) {
453 issues.push(EwfIntegrityAnomaly::UnknownSectionType {
454 offset: pos,
455 type_name: type_name.clone(),
456 });
457 }
458
459 let next = u64::from_le_bytes(desc[16..24].try_into().unwrap());
460 let section_size = u64::from_le_bytes(desc[24..32].try_into().unwrap());
461 let section_end = pos.saturating_add(section_size);
462
463 sections.push(Section {
464 type_name: type_name.clone(),
465 offset: pos,
466 size: section_size,
467 });
468
469 if type_name == "done" || type_name == "next" {
471 break;
472 }
473
474 if next == 0 || next > file_size || next <= pos {
475 issues.push(EwfIntegrityAnomaly::SectionChainBroken {
476 at_offset: pos,
477 next_offset: next,
478 });
479 break;
480 }
481
482 if next > section_end {
483 let gap_offset = section_end;
484 let gap_size = next - section_end;
485 let non_zero = data
486 .get(section_end as usize..next as usize)
487 .map(|s| s.iter().any(|&b| b != 0))
488 .unwrap_or(false);
489 if non_zero {
490 issues.push(EwfIntegrityAnomaly::SectionGapNonZero { gap_offset, gap_size });
491 } else {
492 issues.push(EwfIntegrityAnomaly::SectionGapZero { gap_offset, gap_size });
493 }
494 }
495
496 pos = next;
497 }
498
499 sections
500}
501
502fn check_volume_v1(
503 data: &[u8],
504 desc_offset: u64,
505 issues: &mut Vec<EwfIntegrityAnomaly>,
506) -> Option<VolumeGeometry> {
507 let data_start = (desc_offset as usize) + SECTION_DESCRIPTOR_SIZE;
508 if data.len() < data_start + VOLUME_DATA_MIN {
509 return None;
510 }
511 let vol = &data[data_start..];
512
513 let chunk_count = u32::from_le_bytes(vol[4..8].try_into().unwrap());
514 let sectors_per_chunk = u32::from_le_bytes(vol[8..12].try_into().unwrap());
515 let bytes_per_sector = u32::from_le_bytes(vol[12..16].try_into().unwrap());
516 let sector_count = u64::from_le_bytes(vol[16..24].try_into().unwrap());
517
518 if bytes_per_sector != 512 && bytes_per_sector != 4096 {
519 issues.push(EwfIntegrityAnomaly::BytesPerSectorInvalid { bytes_per_sector });
520 }
521 if sectors_per_chunk == 0 || !sectors_per_chunk.is_power_of_two() {
522 issues.push(EwfIntegrityAnomaly::ChunkSizeInvalid {
523 sectors_per_chunk,
524 bytes_per_sector,
525 });
526 }
527
528 let max_sectors = u64::from(chunk_count) * u64::from(sectors_per_chunk);
529 let min_sectors = max_sectors.saturating_sub(u64::from(sectors_per_chunk));
530 if sectors_per_chunk.is_power_of_two() {
531 let out_of_range =
532 sector_count > max_sectors || (chunk_count > 0 && sector_count <= min_sectors);
533 if out_of_range {
534 issues.push(EwfIntegrityAnomaly::SectorCountMismatch {
535 declared: sector_count,
536 expected: max_sectors,
537 });
538 }
539 }
540
541 Some(VolumeGeometry {
542 chunk_count,
543 sectors_per_chunk,
544 bytes_per_sector,
545 sector_count,
546 })
547}
548
549fn check_table_v1(
550 data: &[u8],
551 desc_offset: u64,
552 volume_chunk_count: Option<u32>,
553 file_size: u64,
554 sectors_range: Option<(u64, u64)>,
555 issues: &mut Vec<EwfIntegrityAnomaly>,
556) {
557 let data_start = (desc_offset as usize) + SECTION_DESCRIPTOR_SIZE;
558 if data.len() < data_start + 24 {
559 return;
560 }
561 let tbl = &data[data_start..];
562 let entry_count = u32::from_le_bytes(tbl[0..4].try_into().unwrap());
563 let base_offset = u64::from_le_bytes(tbl[8..16].try_into().unwrap());
564
565 if let Some(vol_count) = volume_chunk_count {
566 if entry_count != vol_count {
567 issues.push(EwfIntegrityAnomaly::TableChunkCountMismatch {
568 in_volume: vol_count,
569 in_table: entry_count,
570 });
571 }
572 }
573
574 let entries_start = data_start + 24;
575 for i in 0..entry_count {
576 let entry_off = entries_start + (i as usize) * 4;
577 if entry_off + 4 > data.len() {
578 break;
579 }
580 let raw = u32::from_le_bytes(data[entry_off..entry_off + 4].try_into().unwrap());
581 let chunk_rel = u64::from(raw & 0x7FFF_FFFF);
582 let absolute = base_offset.saturating_add(chunk_rel);
583 if absolute >= file_size {
584 issues.push(EwfIntegrityAnomaly::TableEntryOutOfBounds {
585 chunk_index: i,
586 entry_offset: absolute,
587 file_size,
588 });
589 } else if let Some((sec_start, sec_end)) = sectors_range {
590 if absolute < sec_start || absolute >= sec_end {
591 issues.push(EwfIntegrityAnomaly::TableEntryOutsideSectorsRange {
592 chunk_index: i,
593 entry_offset: absolute,
594 sectors_start: sec_start,
595 sectors_end: sec_end,
596 });
597 }
598 }
599 }
600}
601
602fn iter_segment_chunks(data: &[u8], sections: &[Section]) -> Vec<(usize, usize, bool)> {
604 let table = match sections.iter().find(|s| s.type_name == "table") {
605 Some(s) => s,
606 None => return Vec::new(),
607 };
608 let sectors = match sections.iter().find(|s| s.type_name == "sectors") {
609 Some(s) => s,
610 None => return Vec::new(),
611 };
612
613 let tbl_data_start = (table.offset as usize) + SECTION_DESCRIPTOR_SIZE;
614 if data.len() < tbl_data_start + 24 {
615 return Vec::new();
616 }
617 let tbl = &data[tbl_data_start..];
618 let entry_count = u32::from_le_bytes(tbl[0..4].try_into().unwrap()) as usize;
619 let base_offset = u64::from_le_bytes(tbl[8..16].try_into().unwrap()) as usize;
620 let entries_start = tbl_data_start + 24;
621 let sectors_body_end = (sectors.offset + sectors.size) as usize;
622
623 let mut chunks = Vec::with_capacity(entry_count);
624 for i in 0..entry_count {
625 let entry_off = entries_start + i * 4;
626 if entry_off + 4 > data.len() {
627 break;
628 }
629 let raw = u32::from_le_bytes(data[entry_off..entry_off + 4].try_into().unwrap());
630 let compressed = raw & 0x8000_0000 != 0;
631 let rel = (raw & 0x7FFF_FFFF) as usize;
632 let start = base_offset + rel;
633
634 let end = if i + 1 < entry_count {
635 let next_off = entries_start + (i + 1) * 4;
636 if next_off + 4 > data.len() {
637 break;
638 }
639 let next_raw = u32::from_le_bytes(data[next_off..next_off + 4].try_into().unwrap());
640 let next_rel = (next_raw & 0x7FFF_FFFF) as usize;
641 base_offset + next_rel
642 } else {
643 sectors_body_end.min(data.len())
644 };
645
646 if start >= end || end > data.len() {
647 break;
648 }
649 chunks.push((start, end, compressed));
650 }
651 chunks
652}
653
654fn check_hash_all_segments(
656 segments: &[&[u8]],
657 all_sections: &[Vec<Section>],
658 geom: &VolumeGeometry,
659 expected_md5: Option<[u8; 16]>,
660 expected_sha1: Option<[u8; 20]>,
661 issues: &mut Vec<EwfIntegrityAnomaly>,
662) {
663 let chunk_size = u64::from(geom.sectors_per_chunk) * u64::from(geom.bytes_per_sector);
664 let total_bytes = geom.sector_count * u64::from(geom.bytes_per_sector);
665 let mut bytes_remaining = total_bytes;
666
667 let mut md5_h = Md5::new();
668 let mut sha1_h = Sha1::new();
669
670 'outer: for (&seg_data, sections) in segments.iter().zip(all_sections.iter()) {
671 for (start, end, compressed) in iter_segment_chunks(seg_data, sections) {
672 if bytes_remaining == 0 {
673 break 'outer;
674 }
675 let to_hash = bytes_remaining.min(chunk_size) as usize;
676 let raw = &seg_data[start..end];
677
678 if compressed {
679 let limit = (to_hash as u64).saturating_add(1);
680 let mut decompressed = Vec::with_capacity(to_hash);
681 if ZlibDecoder::new(raw)
682 .take(limit)
683 .read_to_end(&mut decompressed)
684 .is_err()
685 {
686 bytes_remaining = bytes_remaining.saturating_sub(to_hash as u64);
687 continue;
688 }
689 let slice = &decompressed[..decompressed.len().min(to_hash)];
690 md5_h.update(slice);
691 sha1_h.update(slice);
692 } else {
693 let slice = &raw[..raw.len().min(to_hash)];
694 md5_h.update(slice);
695 sha1_h.update(slice);
696 }
697 bytes_remaining = bytes_remaining.saturating_sub(to_hash as u64);
698 }
699 }
700
701 let computed_md5: [u8; 16] = md5_h.finalize().into();
702 let computed_sha1: [u8; 20] = sha1_h.finalize().into();
703
704 let last_sections = match all_sections.last() {
705 Some(s) => s,
706 None => return,
707 };
708 let last_data = match segments.last() {
709 Some(d) => d,
710 None => return,
711 };
712
713 match last_sections.iter().find(|s| s.type_name == "hash") {
715 Some(hash_sec) => {
716 let body_start = (hash_sec.offset as usize) + SECTION_DESCRIPTOR_SIZE;
717 if let Some(stored_slice) = last_data.get(body_start..body_start + 16) {
718 let stored: [u8; 16] = stored_slice.try_into().unwrap();
719 if computed_md5 != stored {
720 issues.push(EwfIntegrityAnomaly::HashMismatch {
721 computed: computed_md5,
722 stored,
723 });
724 }
725 }
726 }
727 None => issues.push(EwfIntegrityAnomaly::HashSectionMissing),
728 }
729
730 if let Some(digest_sec) = last_sections.iter().find(|s| s.type_name == "digest") {
732 let body_start = (digest_sec.offset as usize) + SECTION_DESCRIPTOR_SIZE;
733 if let Some(sha1_slice) = last_data.get(body_start + 16..body_start + 36) {
734 let stored: [u8; 20] = sha1_slice.try_into().unwrap();
735 if stored != [0u8; 20] && computed_sha1 != stored {
737 issues.push(EwfIntegrityAnomaly::DigestSha1Mismatch {
738 computed: computed_sha1,
739 stored,
740 });
741 }
742 }
743 }
744
745 if let Some(expected) = expected_md5 {
747 if computed_md5 != expected {
748 issues.push(EwfIntegrityAnomaly::ExternalMd5Mismatch {
749 computed: computed_md5,
750 expected,
751 });
752 }
753 }
754 if let Some(expected) = expected_sha1 {
755 if computed_sha1 != expected {
756 issues.push(EwfIntegrityAnomaly::ExternalSha1Mismatch {
757 computed: computed_sha1,
758 expected,
759 });
760 }
761 }
762}
763
764pub(crate) fn adler32(data: &[u8]) -> u32 {
765 const MOD: u32 = 65521;
766 let mut s1: u32 = 1;
767 let mut s2: u32 = 0;
768 for &b in data {
769 s1 = (s1 + u32::from(b)) % MOD;
770 s2 = (s2 + s1) % MOD;
771 }
772 (s2 << 16) | s1
773}