Skip to main content

ewf_image/
single_files.rs

1use crate::types::{
2    SingleFileAttribute, SingleFileEntry, SingleFileEntryType, SingleFileExtent,
3    SingleFilePermission, SingleFilePermissionGroup, SingleFileSource, SingleFileSubject,
4    SingleFilesInfo,
5};
6use crate::{EwfError, Result};
7
8/// Path separator used by EWF2 logical single-file catalogs.
9///
10/// Paths are represented with tab-separated entry names. A leading separator
11/// refers to the root entry.
12pub const SINGLE_FILE_PATH_SEPARATOR: char = '\t';
13const EXTENDED_ATTRIBUTES_HEADER: &[u8; 37] = &[
14    0x00, 0x00, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x74,
15    0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x62, 0x00, 0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73,
16    0x00, 0x00, 0x00, 0x00, 0x00,
17];
18
19impl SingleFilesInfo {
20    /// Returns the catalog entry at a single-file path.
21    ///
22    /// # Errors
23    ///
24    /// Returns an error if the path contains an empty entry name.
25    pub fn entry_by_path(&self, path: &str) -> Result<Option<&SingleFileEntry>> {
26        self.root.child_by_path(path)
27    }
28
29    /// Returns a source record by its source identifier.
30    pub fn source_by_identifier(&self, identifier: i32) -> Option<&SingleFileSource> {
31        self.sources
32            .iter()
33            .find(|source| source.identifier == Some(identifier))
34    }
35
36    /// Returns a source record by its source table index.
37    pub fn source_by_index(&self, index: i32) -> Option<&SingleFileSource> {
38        usize::try_from(index)
39            .ok()
40            .and_then(|index| self.sources.get(index))
41    }
42
43    /// Returns the source record associated with a catalog entry.
44    pub fn source_for_entry(&self, entry: &SingleFileEntry) -> Option<&SingleFileSource> {
45        let identifier = entry.source_identifier?;
46        if identifier < 1 {
47            return None;
48        }
49        self.source_by_index(identifier)
50    }
51
52    /// Returns a subject record by its subject identifier.
53    pub fn subject_by_identifier(&self, identifier: u32) -> Option<&SingleFileSubject> {
54        self.subjects
55            .iter()
56            .find(|subject| subject.identifier == Some(identifier))
57    }
58
59    /// Returns the subject record associated with a catalog entry.
60    pub fn subject_for_entry(&self, entry: &SingleFileEntry) -> Option<&SingleFileSubject> {
61        self.subject_by_identifier(entry.subject_identifier?)
62    }
63
64    /// Returns the number of permission groups.
65    pub fn number_of_permission_groups(&self) -> usize {
66        self.permission_groups.len()
67    }
68
69    /// Returns a permission group by its table index.
70    pub fn permission_group_by_index(&self, index: i32) -> Option<&SingleFilePermissionGroup> {
71        usize::try_from(index)
72            .ok()
73            .and_then(|index| self.permission_groups.get(index))
74    }
75
76    /// Returns the permission group associated with a catalog entry.
77    pub fn permission_group_for_entry(
78        &self,
79        entry: &SingleFileEntry,
80    ) -> Option<&SingleFilePermissionGroup> {
81        self.permission_group_by_index(entry.permission_group_index?)
82    }
83
84    /// Returns access-control entries associated with a catalog entry.
85    pub fn access_control_entries_for_entry(
86        &self,
87        entry: &SingleFileEntry,
88    ) -> &[SingleFilePermission] {
89        self.permission_group_for_entry(entry)
90            .map_or(&[], |group| group.permissions.as_slice())
91    }
92
93    /// Returns the number of access-control entries for a catalog entry.
94    pub fn number_of_access_control_entries_for_entry(&self, entry: &SingleFileEntry) -> usize {
95        self.access_control_entries_for_entry(entry).len()
96    }
97
98    /// Returns one access-control entry for a catalog entry by index.
99    pub fn access_control_entry_for_entry(
100        &self,
101        entry: &SingleFileEntry,
102        index: usize,
103    ) -> Option<&SingleFilePermission> {
104        self.access_control_entries_for_entry(entry).get(index)
105    }
106}
107
108impl SingleFileSource {
109    /// Returns the source identifier.
110    pub fn identifier(&self) -> Option<i32> {
111        self.identifier
112    }
113
114    /// Returns the source name.
115    pub fn name(&self) -> Option<&str> {
116        self.name.as_deref()
117    }
118
119    /// Returns the evidence number associated with the source.
120    pub fn evidence_number(&self) -> Option<&str> {
121        self.evidence_number.as_deref()
122    }
123
124    /// Returns the source location.
125    pub fn location(&self) -> Option<&str> {
126        self.location.as_deref()
127    }
128
129    /// Returns the source device GUID.
130    pub fn device_guid(&self) -> Option<&str> {
131        self.device_guid.as_deref()
132    }
133
134    /// Returns the primary source device GUID.
135    pub fn primary_device_guid(&self) -> Option<&str> {
136        self.primary_device_guid.as_deref()
137    }
138
139    /// Returns the source manufacturer.
140    pub fn manufacturer(&self) -> Option<&str> {
141        self.manufacturer.as_deref()
142    }
143
144    /// Returns the source model.
145    pub fn model(&self) -> Option<&str> {
146        self.model.as_deref()
147    }
148
149    /// Returns the source serial number.
150    pub fn serial_number(&self) -> Option<&str> {
151        self.serial_number.as_deref()
152    }
153
154    /// Returns the source domain.
155    pub fn domain(&self) -> Option<&str> {
156        self.domain.as_deref()
157    }
158
159    /// Returns the source IP address.
160    pub fn ip_address(&self) -> Option<&str> {
161        self.ip_address.as_deref()
162    }
163
164    /// Returns the source MAC address.
165    pub fn mac_address(&self) -> Option<&str> {
166        self.mac_address.as_deref()
167    }
168
169    /// Returns the source size in bytes.
170    pub fn size(&self) -> Option<u64> {
171        self.size
172    }
173
174    /// Returns the source drive type code.
175    pub fn drive_type(&self) -> Option<char> {
176        self.drive_type
177    }
178
179    /// Returns the source logical offset.
180    pub fn logical_offset(&self) -> Option<i64> {
181        self.logical_offset
182    }
183
184    /// Returns the source physical offset.
185    pub fn physical_offset(&self) -> Option<i64> {
186        self.physical_offset
187    }
188
189    /// Returns the source acquisition timestamp.
190    pub fn acquisition_time(&self) -> Option<i64> {
191        self.acquisition_time
192    }
193
194    /// Returns the source MD5 hash string.
195    pub fn md5_hash(&self) -> Option<&str> {
196        self.md5.as_deref()
197    }
198
199    /// Returns the source SHA1 hash string.
200    pub fn sha1_hash(&self) -> Option<&str> {
201        self.sha1.as_deref()
202    }
203}
204
205impl SingleFileEntry {
206    /// Returns the entry identifier.
207    pub fn identifier(&self) -> Option<u64> {
208        self.identifier
209    }
210
211    /// Returns the entry type.
212    pub fn entry_type(&self) -> Option<SingleFileEntryType> {
213        self.file_entry_type
214    }
215
216    /// Returns the raw entry flags.
217    pub fn flags(&self) -> Option<u32> {
218        self.flags
219    }
220
221    /// Returns the entry GUID.
222    pub fn guid(&self) -> Option<&str> {
223        self.guid.as_deref()
224    }
225
226    /// Returns the logical media offset for entry data.
227    pub fn media_data_offset(&self) -> Option<i64> {
228        self.logical_offset
229    }
230
231    /// Returns the entry data size in bytes.
232    pub fn media_data_size(&self) -> Option<u64> {
233        self.size
234    }
235
236    /// Returns the physical source offset for entry data.
237    pub fn physical_offset(&self) -> Option<i64> {
238        self.physical_offset
239    }
240
241    /// Returns the duplicate-data logical media offset.
242    pub fn duplicate_media_data_offset(&self) -> Option<i64> {
243        self.duplicate_data_offset
244    }
245
246    /// Returns the associated source identifier.
247    pub fn source_identifier(&self) -> Option<i32> {
248        self.source_identifier
249    }
250
251    /// Returns the associated subject identifier.
252    pub fn subject_identifier(&self) -> Option<u32> {
253        self.subject_identifier
254    }
255
256    /// Returns the associated permission group index.
257    pub fn permission_group_index(&self) -> Option<i32> {
258        self.permission_group_index
259    }
260
261    /// Returns the raw record type.
262    pub fn record_type(&self) -> Option<u32> {
263        self.record_type
264    }
265
266    /// Returns the entry name.
267    pub fn name(&self) -> Option<&str> {
268        self.name.as_deref()
269    }
270
271    /// Returns the short entry name.
272    pub fn short_name(&self) -> Option<&str> {
273        self.short_name.as_deref()
274    }
275
276    /// Returns the entry size in bytes.
277    pub fn size(&self) -> Option<u64> {
278        self.size
279    }
280
281    /// Returns the creation timestamp.
282    pub fn creation_time(&self) -> Option<i64> {
283        self.creation_time
284    }
285
286    /// Returns the modification timestamp.
287    pub fn modification_time(&self) -> Option<i64> {
288        self.modification_time
289    }
290
291    /// Returns the access timestamp.
292    pub fn access_time(&self) -> Option<i64> {
293        self.access_time
294    }
295
296    /// Returns the entry metadata modification timestamp.
297    pub fn entry_modification_time(&self) -> Option<i64> {
298        self.entry_modification_time
299    }
300
301    /// Returns the deletion timestamp.
302    pub fn deletion_time(&self) -> Option<i64> {
303        self.deletion_time
304    }
305
306    /// Returns the entry MD5 hash string.
307    pub fn md5_hash(&self) -> Option<&str> {
308        self.md5.as_deref()
309    }
310
311    /// Returns the entry SHA1 hash string.
312    pub fn sha1_hash(&self) -> Option<&str> {
313        self.sha1.as_deref()
314    }
315
316    /// Returns the number of child entries.
317    pub fn number_of_sub_file_entries(&self) -> usize {
318        self.children.len()
319    }
320
321    /// Returns a child entry by index.
322    pub fn sub_file_entry(&self, index: usize) -> Option<&SingleFileEntry> {
323        self.children.get(index)
324    }
325
326    /// Returns a child entry by name.
327    pub fn sub_file_entry_by_name(&self, name: &str) -> Option<&SingleFileEntry> {
328        self.child_by_name(name)
329    }
330
331    /// Returns a descendant entry by single-file path.
332    ///
333    /// # Errors
334    ///
335    /// Returns an error if the path contains an empty entry name.
336    pub fn sub_file_entry_by_path(&self, path: &str) -> Result<Option<&SingleFileEntry>> {
337        self.child_by_path(path)
338    }
339
340    /// Returns the number of data extents.
341    pub fn number_of_extents(&self) -> usize {
342        self.extents.len()
343    }
344
345    /// Returns a data extent by index.
346    pub fn extent(&self, index: usize) -> Option<&SingleFileExtent> {
347        self.extents.get(index)
348    }
349
350    /// Returns the number of extended attributes.
351    pub fn number_of_attributes(&self) -> usize {
352        self.attributes.len()
353    }
354
355    /// Returns an extended attribute by index.
356    pub fn attribute(&self, index: usize) -> Option<&SingleFileAttribute> {
357        self.attributes.get(index)
358    }
359
360    /// Returns a direct child entry by name.
361    pub fn child_by_name(&self, name: &str) -> Option<&SingleFileEntry> {
362        self.children
363            .iter()
364            .find(|child| child.name.as_deref() == Some(name))
365    }
366
367    /// Returns a descendant entry by single-file path.
368    ///
369    /// # Errors
370    ///
371    /// Returns an error if the path contains an empty entry name.
372    pub fn child_by_path(&self, path: &str) -> Result<Option<&SingleFileEntry>> {
373        entry_by_path(self, path)
374    }
375}
376
377impl SingleFilePermission {
378    /// Returns the permission name.
379    pub fn name(&self) -> Option<&str> {
380        self.name.as_deref()
381    }
382
383    /// Returns the permission identifier.
384    pub fn identifier(&self) -> Option<&str> {
385        self.identifier.as_deref()
386    }
387
388    /// Returns the permission property type.
389    pub fn property_type(&self) -> Option<u32> {
390        self.property_type
391    }
392
393    /// Returns the access mask.
394    pub fn access_mask(&self) -> Option<u32> {
395        self.access_mask
396    }
397
398    /// Returns the ACE flags.
399    pub fn flags(&self) -> Option<u32> {
400        self.ace_flags
401    }
402}
403
404impl SingleFilePermissionGroup {
405    /// Returns the group name.
406    pub fn name(&self) -> Option<&str> {
407        self.name.as_deref()
408    }
409
410    /// Returns the group identifier.
411    pub fn identifier(&self) -> Option<&str> {
412        self.identifier.as_deref()
413    }
414
415    /// Returns the group property type.
416    pub fn property_type(&self) -> Option<u32> {
417        self.property_type
418    }
419
420    /// Returns the group access mask.
421    pub fn access_mask(&self) -> Option<u32> {
422        self.access_mask
423    }
424
425    /// Returns the group ACE flags.
426    pub fn flags(&self) -> Option<u32> {
427        self.ace_flags
428    }
429
430    /// Returns the number of permissions in the group.
431    pub fn number_of_entries(&self) -> usize {
432        self.permissions.len()
433    }
434
435    /// Returns a permission in the group by table index.
436    pub fn entry_by_index(&self, index: i32) -> Option<&SingleFilePermission> {
437        usize::try_from(index)
438            .ok()
439            .and_then(|index| self.permissions.get(index))
440    }
441}
442
443impl SingleFileAttribute {
444    /// Returns the attribute name.
445    pub fn name(&self) -> Option<&str> {
446        self.name.as_deref()
447    }
448
449    /// Returns the attribute value.
450    pub fn value(&self) -> Option<&str> {
451        self.value.as_deref()
452    }
453}
454
455pub(crate) fn parse_ewf2_single_files_data(data: &[u8]) -> Result<SingleFilesInfo> {
456    let lines = decode_utf16le_lines(data)?;
457    if lines.first().map(String::as_str) != Some("5") {
458        return Err(EwfError::Malformed(
459            "EWF2 single files data has unsupported category count".into(),
460        ));
461    }
462
463    let mut category_index = 1;
464
465    let (data_size, next_category_index) = parse_record_category(&lines, category_index)?;
466    category_index = next_category_index;
467    let (permission_groups, next_category_index) =
468        parse_permission_groups_category(&lines, category_index)?;
469    category_index = next_category_index;
470    let (sources, next_category_index) = parse_sources_category(&lines, category_index)?;
471    category_index = next_category_index;
472    let (subjects, entry_index) = parse_subjects_category(&lines, category_index)?;
473
474    require_category_at(&lines, entry_index, "entry", "entry category")?;
475    let types_line_index = entry_index
476        .checked_add(2)
477        .ok_or_else(|| EwfError::Malformed("EWF2 single files entry index overflow".into()))?;
478    let mut cursor = entry_index
479        .checked_add(3)
480        .ok_or_else(|| EwfError::Malformed("EWF2 single files entry index overflow".into()))?;
481    if types_line_index >= lines.len() {
482        return Err(EwfError::Malformed(
483            "EWF2 single files entry category is truncated".into(),
484        ));
485    }
486
487    parse_category_entry_count(&lines[entry_index + 1], "EWF2 single files entry count")?;
488    let types = parse_type_line(
489        &lines[types_line_index],
490        "EWF2 single files entry category types",
491    )?;
492
493    let data_size = data_size.unwrap_or(
494        u64::try_from(data.len())
495            .map_err(|_| EwfError::Malformed("single files data size overflow".into()))?,
496    );
497    let root = parse_entry(&lines, &types, &mut cursor)?;
498    require_empty_category_terminator(
499        &lines,
500        cursor,
501        "EWF2 single files entry category terminator",
502    )?;
503    Ok(SingleFilesInfo {
504        data_size,
505        root,
506        sources,
507        subjects,
508        permission_groups,
509    })
510}
511
512fn entry_by_path<'a>(
513    mut entry: &'a SingleFileEntry,
514    path: &str,
515) -> Result<Option<&'a SingleFileEntry>> {
516    let mut rest = path
517        .strip_prefix(SINGLE_FILE_PATH_SEPARATOR)
518        .unwrap_or(path);
519    if rest.is_empty() {
520        return Ok(Some(entry));
521    }
522
523    while !rest.is_empty() {
524        let (segment, next_rest) = match rest.find([SINGLE_FILE_PATH_SEPARATOR, '\0']) {
525            Some(separator_index) => {
526                let separator_size = rest[separator_index..]
527                    .chars()
528                    .next()
529                    .expect("separator index points to a character")
530                    .len_utf8();
531                (
532                    &rest[..separator_index],
533                    &rest[separator_index + separator_size..],
534                )
535            }
536            None => (rest, ""),
537        };
538        if segment.is_empty() {
539            return Err(EwfError::Malformed(
540                "single file path is missing entry name".into(),
541            ));
542        }
543
544        entry = match entry.child_by_name(segment) {
545            Some(child) => child,
546            None => return Ok(None),
547        };
548        rest = next_rest;
549    }
550
551    Ok(Some(entry))
552}
553
554fn parse_sources_category(
555    lines: &[String],
556    category_index: usize,
557) -> Result<(Vec<SingleFileSource>, usize)> {
558    require_category_at(lines, category_index, "srce", "source category")?;
559    let (types, entry_count, mut cursor) = parse_category_header(lines, category_index, "srce")?;
560
561    let root = parse_source_row(&types, get_line(lines, cursor, "EWF2 source root")?)?;
562    cursor += 1;
563    let mut sources = Vec::with_capacity(entry_count.saturating_add(1));
564    sources.push(root);
565
566    for source_index in 0..entry_count {
567        let child_count = parse_child_entry_count(
568            get_line(lines, cursor, "EWF2 source child count")?,
569            "EWF2 source child count",
570        )?;
571        if child_count != 0 {
572            return Err(EwfError::Malformed(
573                "EWF2 source entries cannot have children".into(),
574            ));
575        }
576        cursor += 1;
577
578        let source = parse_source_row(&types, get_line(lines, cursor, "EWF2 source row")?)?;
579        let expected_identifier = source_index
580            .checked_add(1)
581            .and_then(|identifier| i32::try_from(identifier).ok())
582            .ok_or_else(|| EwfError::Malformed("EWF2 source identifier index overflow".into()))?;
583        if source.identifier != Some(expected_identifier) {
584            return Err(EwfError::Malformed(
585                "EWF2 source identifier does not match source index".into(),
586            ));
587        }
588        sources.push(source);
589        cursor += 1;
590    }
591    require_empty_category_terminator(
592        lines,
593        cursor,
594        "EWF2 single files source category terminator",
595    )?;
596    Ok((
597        sources,
598        next_category_index(cursor, "EWF2 source category terminator")?,
599    ))
600}
601
602fn parse_subjects_category(
603    lines: &[String],
604    category_index: usize,
605) -> Result<(Vec<SingleFileSubject>, usize)> {
606    require_category_at(lines, category_index, "sub", "subject category")?;
607    let (types, entry_count, mut cursor) = parse_category_header(lines, category_index, "sub")?;
608
609    let root = parse_subject_row(&types, get_line(lines, cursor, "EWF2 subject root")?)?;
610    cursor += 1;
611    let mut subjects = Vec::with_capacity(entry_count.saturating_add(1));
612    subjects.push(root);
613
614    for _ in 0..entry_count {
615        let child_count = parse_child_entry_count(
616            get_line(lines, cursor, "EWF2 subject child count")?,
617            "EWF2 subject child count",
618        )?;
619        if child_count != 0 {
620            return Err(EwfError::Malformed(
621                "EWF2 subject entries cannot have children".into(),
622            ));
623        }
624        cursor += 1;
625
626        let subject = parse_subject_row(&types, get_line(lines, cursor, "EWF2 subject row")?)?;
627        subjects.push(subject);
628        cursor += 1;
629    }
630    require_empty_category_terminator(
631        lines,
632        cursor,
633        "EWF2 single files subject category terminator",
634    )?;
635    Ok((
636        subjects,
637        next_category_index(cursor, "EWF2 subject category terminator")?,
638    ))
639}
640
641fn parse_permission_groups_category(
642    lines: &[String],
643    category_index: usize,
644) -> Result<(Vec<SingleFilePermissionGroup>, usize)> {
645    require_category_at(lines, category_index, "perm", "permission category")?;
646    let (types, entry_count, mut cursor) = parse_category_header(lines, category_index, "perm")?;
647
648    let root_permission = parse_permission_row(
649        &types,
650        get_line(lines, cursor, "EWF2 permission category root")?,
651    )?;
652    require_permission_group_type(&root_permission, "EWF2 permission category root")?;
653    cursor += 1;
654    let mut groups = Vec::with_capacity(entry_count);
655
656    for _ in 0..entry_count {
657        let permission_count = parse_child_entry_count(
658            get_line(lines, cursor, "EWF2 permission group child count")?,
659            "EWF2 permission group child count",
660        )?;
661        cursor += 1;
662
663        let group_permission = parse_permission_row(
664            &types,
665            get_line(lines, cursor, "EWF2 permission group row")?,
666        )?;
667        require_permission_group_type(&group_permission, "EWF2 permission group row")?;
668        cursor += 1;
669        let mut group = permission_group_from_permission(group_permission, permission_count);
670
671        for _ in 0..permission_count {
672            let child_count = parse_child_entry_count(
673                get_line(lines, cursor, "EWF2 permission child count")?,
674                "EWF2 permission child count",
675            )?;
676            if child_count != 0 {
677                return Err(EwfError::Malformed(
678                    "EWF2 permission entries cannot have children".into(),
679                ));
680            }
681            cursor += 1;
682
683            let permission =
684                parse_permission_row(&types, get_line(lines, cursor, "EWF2 permission row")?)?;
685            group.permissions.push(permission);
686            cursor += 1;
687        }
688        groups.push(group);
689    }
690    require_empty_category_terminator(
691        lines,
692        cursor,
693        "EWF2 single files permission category terminator",
694    )?;
695    Ok((
696        groups,
697        next_category_index(cursor, "EWF2 permission category terminator")?,
698    ))
699}
700
701fn require_category_at(lines: &[String], index: usize, name: &str, label: &str) -> Result<()> {
702    let line = get_line(lines, index, &format!("EWF2 single files {label}"))?;
703    if line != name {
704        return Err(EwfError::Malformed(format!(
705            "EWF2 single files {label} is missing or out of order"
706        )));
707    }
708    Ok(())
709}
710
711fn next_category_index(index: usize, label: &str) -> Result<usize> {
712    index
713        .checked_add(1)
714        .ok_or_else(|| EwfError::Malformed(format!("{label} index overflow")))
715}
716
717fn parse_record_category(lines: &[String], category_index: usize) -> Result<(Option<u64>, usize)> {
718    require_category_at(lines, category_index, "rec", "record category")?;
719    let types_line_index = category_index
720        .checked_add(1)
721        .ok_or_else(|| EwfError::Malformed("EWF2 record category index overflow".into()))?;
722    let values_line_index = category_index
723        .checked_add(2)
724        .ok_or_else(|| EwfError::Malformed("EWF2 record category index overflow".into()))?;
725    let types = parse_type_line(
726        get_line(lines, types_line_index, "EWF2 record category types")?,
727        "EWF2 record category types",
728    )?;
729    let values: Vec<&str> = get_line(lines, values_line_index, "EWF2 record category values")?
730        .split('\t')
731        .collect();
732    let terminator_index = category_index
733        .checked_add(3)
734        .ok_or_else(|| EwfError::Malformed("EWF2 record category index overflow".into()))?;
735
736    require_empty_category_terminator(
737        lines,
738        terminator_index,
739        "EWF2 single files record category terminator",
740    )?;
741    let next_category_index =
742        next_category_index(terminator_index, "EWF2 record category terminator")?;
743
744    for (index, value_type) in types.iter().enumerate() {
745        if *value_type != "tb" {
746            continue;
747        }
748        let Some(value) = values.get(index).copied().filter(|value| !value.is_empty()) else {
749            return Ok((None, next_category_index));
750        };
751        return Ok((
752            parse_u64(value, "record data size").map(Some)?,
753            next_category_index,
754        ));
755    }
756    Ok((None, next_category_index))
757}
758
759fn parse_category_header<'a>(
760    lines: &'a [String],
761    category_index: usize,
762    label: &str,
763) -> Result<(Vec<&'a str>, usize, usize)> {
764    let count_line_index = category_index
765        .checked_add(1)
766        .ok_or_else(|| EwfError::Malformed(format!("EWF2 {label} category index overflow")))?;
767    let types_line_index = category_index
768        .checked_add(2)
769        .ok_or_else(|| EwfError::Malformed(format!("EWF2 {label} category index overflow")))?;
770    let copy_count_line_index = category_index
771        .checked_add(3)
772        .ok_or_else(|| EwfError::Malformed(format!("EWF2 {label} category index overflow")))?;
773    let root_line_index = category_index
774        .checked_add(4)
775        .ok_or_else(|| EwfError::Malformed(format!("EWF2 {label} category index overflow")))?;
776
777    let entry_count = parse_category_entry_count(
778        get_line(lines, count_line_index, "EWF2 category count")?,
779        "EWF2 category count",
780    )?;
781    let types = parse_type_line(
782        get_line(lines, types_line_index, "EWF2 category types")?,
783        &format!("EWF2 {label} category types"),
784    )?;
785    let copy_entry_count = parse_child_entry_count(
786        get_line(lines, copy_count_line_index, "EWF2 category copy count")?,
787        "EWF2 category copy count",
788    )?;
789    if copy_entry_count != entry_count {
790        return Err(EwfError::Malformed(format!(
791            "EWF2 {label} category entry count mismatch"
792        )));
793    }
794    Ok((types, entry_count, root_line_index))
795}
796
797fn parse_source_row(types: &[&str], row: &str) -> Result<SingleFileSource> {
798    let values = split_exact_typed_values(types, row, "EWF2 source row")?;
799    let mut source = SingleFileSource::default();
800    for (value_type, value) in types.iter().zip(values.iter()) {
801        if value.is_empty() {
802            continue;
803        }
804        match *value_type {
805            "ah" => source.md5 = parse_serialized_base16_string(value, "source MD5 hash")?,
806            "aq" => source.acquisition_time = Some(parse_i64(value, "source acquisition time")?),
807            "do" => source.domain = Some((*value).to_owned()),
808            "dt" => source.drive_type = Some(parse_single_char(value, "source drive type")?),
809            "ev" => source.evidence_number = Some((*value).to_owned()),
810            "gu" => {
811                source.device_guid = parse_serialized_base16_string(value, "source device GUID")?;
812            }
813            "id" => source.identifier = Some(parse_non_negative_i32(value, "source identifier")?),
814            "ip" => source.ip_address = Some((*value).to_owned()),
815            "lo" => source.logical_offset = Some(parse_i64(value, "source logical offset")?),
816            "loc" => source.location = Some((*value).to_owned()),
817            "ma" => {
818                source.mac_address = parse_serialized_base16_string(value, "source MAC address")?;
819            }
820            "mfr" => source.manufacturer = Some((*value).to_owned()),
821            "mo" => source.model = Some((*value).to_owned()),
822            "n" => source.name = Some((*value).to_owned()),
823            "pgu" => {
824                source.primary_device_guid =
825                    parse_serialized_base16_string(value, "source primary device GUID")?;
826            }
827            "po" => source.physical_offset = Some(parse_i64(value, "source physical offset")?),
828            "se" => source.serial_number = Some((*value).to_owned()),
829            "sh" => source.sha1 = parse_serialized_base16_string(value, "source SHA1 hash")?,
830            "tb" => source.size = Some(parse_u64(value, "source size")?),
831            _ => {}
832        }
833    }
834    Ok(source)
835}
836
837fn parse_subject_row(types: &[&str], row: &str) -> Result<SingleFileSubject> {
838    let values = split_exact_typed_values(types, row, "EWF2 subject row")?;
839    let mut subject = SingleFileSubject::default();
840    for (value_type, value) in types.iter().zip(values.iter()) {
841        if value.is_empty() {
842            continue;
843        }
844        match *value_type {
845            "id" => subject.identifier = Some(parse_u32(value, "subject identifier")?),
846            "n" => subject.name = Some((*value).to_owned()),
847            _ => {}
848        }
849    }
850    Ok(subject)
851}
852
853fn parse_permission_row(types: &[&str], row: &str) -> Result<SingleFilePermission> {
854    let values = split_exact_typed_values(types, row, "EWF2 permission row")?;
855    let mut permission = SingleFilePermission::default();
856    for (value_type, value) in types.iter().zip(values.iter()) {
857        if value.is_empty() {
858            continue;
859        }
860        match *value_type {
861            "n" => permission.name = Some((*value).to_owned()),
862            "nta" => permission.access_mask = Some(parse_u32(value, "permission access mask")?),
863            "nti" => permission.ace_flags = Some(parse_u32(value, "permission ACE flags")?),
864            "pr" | "pt" => {
865                permission.property_type = Some(parse_u32(value, "permission property type")?);
866            }
867            "s" => permission.identifier = Some((*value).to_owned()),
868            _ => {}
869        }
870    }
871    Ok(permission)
872}
873
874fn require_permission_group_type(permission: &SingleFilePermission, label: &str) -> Result<()> {
875    if permission.property_type != Some(10) {
876        return Err(EwfError::Malformed(format!(
877            "{label} has unsupported permission group type"
878        )));
879    }
880    Ok(())
881}
882
883fn permission_group_from_permission(
884    permission: SingleFilePermission,
885    permission_count: usize,
886) -> SingleFilePermissionGroup {
887    SingleFilePermissionGroup {
888        name: permission.name,
889        identifier: permission.identifier,
890        property_type: permission.property_type,
891        access_mask: permission.access_mask,
892        ace_flags: permission.ace_flags,
893        permissions: Vec::with_capacity(permission_count),
894    }
895}
896
897fn split_typed_values<'a>(types: &[&str], row: &'a str) -> Vec<&'a str> {
898    let mut values: Vec<&str> = row.split('\t').collect();
899    values.truncate(types.len());
900    values.resize(types.len(), "");
901    values
902}
903
904fn split_exact_typed_values<'a>(types: &[&str], row: &'a str, label: &str) -> Result<Vec<&'a str>> {
905    let values: Vec<&str> = row.split('\t').collect();
906    if values.len() != types.len() {
907        return Err(EwfError::Malformed(format!(
908            "{label} value count does not match type count"
909        )));
910    }
911    Ok(values)
912}
913
914fn parse_type_line<'a>(line: &'a str, label: &str) -> Result<Vec<&'a str>> {
915    let types: Vec<&str> = line.split('\t').collect();
916    if types.is_empty() || types.iter().any(|value_type| value_type.is_empty()) {
917        return Err(EwfError::Malformed(format!("{label} has empty type")));
918    }
919    Ok(types)
920}
921
922fn parse_category_entry_count(line: &str, label: &str) -> Result<usize> {
923    let (first, second) = parse_number_pair(line, label)?;
924    if second != 1 {
925        return Err(EwfError::Malformed(format!(
926            "{label} has unsupported category count shape"
927        )));
928    }
929    usize::try_from(first).map_err(|_| EwfError::Malformed(format!("{label} does not fit usize")))
930}
931
932fn parse_child_entry_count(line: &str, label: &str) -> Result<usize> {
933    let (first, second) = parse_number_pair(line, label)?;
934    if first != 0 {
935        return Err(EwfError::Malformed(format!(
936            "{label} has unsupported child count shape"
937        )));
938    }
939    Ok(second)
940}
941
942fn parse_file_entry_child_count(line: &str, label: &str) -> Result<usize> {
943    let (parent_value, child_count) = parse_number_pair(line, label)?;
944    if parent_value != 0 && parent_value != 26 {
945        return Err(EwfError::Malformed(format!(
946            "{label} has unsupported parent value"
947        )));
948    }
949    Ok(child_count)
950}
951
952fn get_line<'a>(lines: &'a [String], index: usize, label: &str) -> Result<&'a str> {
953    lines
954        .get(index)
955        .map(String::as_str)
956        .ok_or_else(|| EwfError::Malformed(format!("{label} is truncated")))
957}
958
959fn require_empty_category_terminator(lines: &[String], cursor: usize, label: &str) -> Result<()> {
960    if !get_line(lines, cursor, label)?.is_empty() {
961        return Err(EwfError::Malformed(format!("{label} is not empty")));
962    }
963    Ok(())
964}
965
966fn parse_single_char(value: &str, label: &str) -> Result<char> {
967    let mut characters = value.chars();
968    let character = characters
969        .next()
970        .ok_or_else(|| EwfError::Malformed(format!("{label} is empty")))?;
971    if characters.next().is_some() {
972        return Err(EwfError::Malformed(format!(
973            "{label} has more than one character"
974        )));
975    }
976    Ok(character)
977}
978
979fn decode_utf16le_lines(data: &[u8]) -> Result<Vec<String>> {
980    if !data.len().is_multiple_of(2) {
981        return Err(EwfError::Malformed(
982            "EWF2 single files data has odd UTF-16 size".into(),
983        ));
984    }
985
986    let units: Vec<u16> = data
987        .chunks_exact(2)
988        .map(|chunk| u16::from_le_bytes(chunk.try_into().expect("chunk size checked")))
989        .collect();
990    let text = String::from_utf16(&units)
991        .map_err(|_| EwfError::Malformed("EWF2 single files data is not valid UTF-16LE".into()))?;
992    let text = text.strip_prefix('\u{feff}').unwrap_or(&text);
993
994    Ok(text
995        .split('\n')
996        .map(|line| line.strip_suffix('\r').unwrap_or(line).to_owned())
997        .collect())
998}
999
1000fn parse_entry(lines: &[String], types: &[&str], cursor: &mut usize) -> Result<SingleFileEntry> {
1001    let count_line = lines
1002        .get(*cursor)
1003        .ok_or_else(|| EwfError::Malformed("EWF2 single file entry missing count".into()))?;
1004    let child_count =
1005        parse_file_entry_child_count(count_line, "EWF2 single file entry child count")?;
1006    *cursor = cursor
1007        .checked_add(1)
1008        .ok_or_else(|| EwfError::Malformed("EWF2 single file entry index overflow".into()))?;
1009
1010    let value_line = lines
1011        .get(*cursor)
1012        .ok_or_else(|| EwfError::Malformed("EWF2 single file entry missing values".into()))?;
1013    let values = split_typed_values(types, value_line);
1014    *cursor = cursor
1015        .checked_add(1)
1016        .ok_or_else(|| EwfError::Malformed("EWF2 single file entry index overflow".into()))?;
1017
1018    let mut entry = SingleFileEntry::default();
1019    for (value_type, value) in types.iter().zip(values.iter()) {
1020        apply_entry_value(&mut entry, value_type, value)?;
1021    }
1022
1023    for _ in 0..child_count {
1024        let child = parse_entry(lines, types, cursor)?;
1025        entry.children.push(child);
1026    }
1027    Ok(entry)
1028}
1029
1030fn apply_entry_value(entry: &mut SingleFileEntry, value_type: &str, value: &str) -> Result<()> {
1031    if value.is_empty() {
1032        return Ok(());
1033    }
1034
1035    match value_type {
1036        "ac" => entry.access_time = Some(parse_i64(value, "access time")?),
1037        "be" => entry.extents = parse_binary_extents(value)?,
1038        "cid" => entry.record_type = Some(parse_u32(value, "record type")?),
1039        "cr" => entry.creation_time = Some(parse_i64(value, "creation time")?),
1040        "dl" => entry.deletion_time = Some(parse_i64(value, "deletion time")?),
1041        "du" => entry.duplicate_data_offset = Some(parse_i64(value, "duplicate data offset")?),
1042        "ea" => entry.attributes = parse_extended_attributes(value)?,
1043        "ha" => entry.md5 = parse_serialized_base16_string(value, "MD5 hash")?,
1044        "id" => entry.identifier = Some(parse_u64(value, "identifier")?),
1045        "lo" => entry.logical_offset = Some(parse_i64(value, "logical offset")?),
1046        "ls" => entry.size = Some(parse_u64(value, "file size")?),
1047        "mid" => entry.guid = parse_serialized_base16_string(value, "GUID")?,
1048        "mo" => entry.entry_modification_time = Some(parse_i64(value, "entry modification time")?),
1049        "n" => entry.name = Some(value.to_owned()),
1050        "opr" => entry.flags = Some(parse_u32(value, "entry flags")?),
1051        "p" => {
1052            entry.file_entry_type = Some(match value {
1053                "f" => SingleFileEntryType::File,
1054                "d" => SingleFileEntryType::Directory,
1055                _ => SingleFileEntryType::Unknown,
1056            });
1057        }
1058        "pm" => entry.permission_group_index = Some(parse_i32(value, "permission group index")?),
1059        "po" => entry.physical_offset = Some(parse_i64(value, "physical offset")?),
1060        "sha" => entry.sha1 = parse_serialized_base16_string(value, "SHA1 hash")?,
1061        "snh" => entry.short_name = Some(parse_short_name(value)?),
1062        "src" => {
1063            entry.source_identifier = Some(parse_non_negative_i32(value, "source identifier")?);
1064        }
1065        "sub" => entry.subject_identifier = Some(parse_u32(value, "subject identifier")?),
1066        "wr" => entry.modification_time = Some(parse_i64(value, "modification time")?),
1067        _ => {}
1068    }
1069    Ok(())
1070}
1071
1072fn parse_binary_extents(value: &str) -> Result<Vec<SingleFileExtent>> {
1073    let mut parts = value.split(' ').filter(|part| !part.is_empty()).peekable();
1074    let extent_count = parts
1075        .next()
1076        .ok_or_else(|| EwfError::Malformed("EWF2 single files extents missing count".into()))
1077        .and_then(|part| parse_hex_usize(part, "extent count"))?;
1078    let mut extents = Vec::with_capacity(extent_count);
1079
1080    while parts.peek().is_some() {
1081        let sparse = if parts.peek() == Some(&"S") {
1082            parts.next();
1083            true
1084        } else {
1085            false
1086        };
1087        let data_offset = parts
1088            .next()
1089            .ok_or_else(|| EwfError::Malformed("EWF2 single files extent missing offset".into()))
1090            .and_then(|part| parse_hex_u64(part, "extent offset"))?;
1091        let data_size = parts
1092            .next()
1093            .ok_or_else(|| EwfError::Malformed("EWF2 single files extent missing size".into()))
1094            .and_then(|part| parse_hex_u64(part, "extent size"))?;
1095        extents.push(SingleFileExtent {
1096            data_offset,
1097            data_size,
1098            sparse,
1099        });
1100    }
1101
1102    if extents.len() != extent_count {
1103        return Err(EwfError::Malformed(
1104            "EWF2 single files extent count mismatch".into(),
1105        ));
1106    }
1107    Ok(extents)
1108}
1109
1110fn parse_short_name(value: &str) -> Result<String> {
1111    let mut parts = value.split(' ');
1112    let declared_size = parts
1113        .next()
1114        .ok_or_else(|| EwfError::Malformed("short name is missing size".into()))?;
1115    let short_name = parts
1116        .next()
1117        .ok_or_else(|| EwfError::Malformed("short name is missing value".into()))?;
1118    if parts.next().is_some() {
1119        return Err(EwfError::Malformed(
1120            "short name has unsupported value count".into(),
1121        ));
1122    }
1123    let declared_size = parse_u64(declared_size, "short name size")?;
1124    let actual_size = short_name
1125        .len()
1126        .checked_add(1)
1127        .ok_or_else(|| EwfError::Malformed("short name size overflow".into()))?;
1128    let actual_size = u64::try_from(actual_size)
1129        .map_err(|_| EwfError::Malformed("short name size does not fit u64".into()))?;
1130    if declared_size != actual_size {
1131        return Err(EwfError::Malformed(
1132            "short name size does not match value size".into(),
1133        ));
1134    }
1135    Ok(short_name.to_owned())
1136}
1137
1138fn parse_extended_attributes(value: &str) -> Result<Vec<SingleFileAttribute>> {
1139    let data = parse_hex_byte_stream(value, "extended attributes")?;
1140    if data.is_empty() {
1141        return Ok(Vec::new());
1142    }
1143    if data.len() < EXTENDED_ATTRIBUTES_HEADER.len()
1144        || &data[..EXTENDED_ATTRIBUTES_HEADER.len()] != EXTENDED_ATTRIBUTES_HEADER
1145    {
1146        return Err(EwfError::Malformed(
1147            "EWF2 single files extended attributes header is unsupported".into(),
1148        ));
1149    }
1150
1151    let mut attributes = Vec::new();
1152    let mut offset = 0_usize;
1153    while offset < data.len() {
1154        let (attribute, read_size) = parse_extended_attribute_record(&data[offset..])?;
1155        offset = offset
1156            .checked_add(read_size)
1157            .ok_or_else(|| EwfError::Malformed("extended attribute offset overflow".into()))?;
1158        if !attribute.is_branch {
1159            attributes.push(SingleFileAttribute {
1160                name: attribute.name,
1161                value: attribute.value,
1162            });
1163        }
1164    }
1165    Ok(attributes)
1166}
1167
1168struct ParsedExtendedAttribute {
1169    name: Option<String>,
1170    value: Option<String>,
1171    is_branch: bool,
1172}
1173
1174fn parse_extended_attribute_record(data: &[u8]) -> Result<(ParsedExtendedAttribute, usize)> {
1175    if data.len() < 13 {
1176        return Err(EwfError::Malformed(
1177            "EWF2 single files extended attribute is truncated".into(),
1178        ));
1179    }
1180
1181    let is_branch = data[4] != 0;
1182    let name_units = u32::from_le_bytes(data[5..9].try_into().expect("slice length checked"));
1183    let value_units = u32::from_le_bytes(data[9..13].try_into().expect("slice length checked"));
1184    let name_size = utf16_byte_size(name_units, "extended attribute name")?;
1185    let value_size = utf16_byte_size(value_units, "extended attribute value")?;
1186    let value_offset = 13_usize
1187        .checked_add(name_size)
1188        .ok_or_else(|| EwfError::Malformed("extended attribute value offset overflow".into()))?;
1189    let end_offset = value_offset
1190        .checked_add(value_size)
1191        .ok_or_else(|| EwfError::Malformed("extended attribute end offset overflow".into()))?;
1192    if data.len() < end_offset {
1193        return Err(EwfError::Malformed(
1194            "EWF2 single files extended attribute is truncated".into(),
1195        ));
1196    }
1197
1198    let name = decode_optional_utf16le_string(&data[13..value_offset], "extended attribute name")?;
1199    let value = decode_optional_utf16le_string(
1200        &data[value_offset..end_offset],
1201        "extended attribute value",
1202    )?;
1203
1204    Ok((
1205        ParsedExtendedAttribute {
1206            name,
1207            value,
1208            is_branch,
1209        },
1210        end_offset,
1211    ))
1212}
1213
1214fn utf16_byte_size(units: u32, label: &str) -> Result<usize> {
1215    let units = usize::try_from(units)
1216        .map_err(|_| EwfError::Malformed(format!("{label} size does not fit usize")))?;
1217    units
1218        .checked_mul(2)
1219        .ok_or_else(|| EwfError::Malformed(format!("{label} size overflow")))
1220}
1221
1222fn decode_optional_utf16le_string(data: &[u8], label: &str) -> Result<Option<String>> {
1223    if data.is_empty() {
1224        return Ok(None);
1225    }
1226    if !data.len().is_multiple_of(2) {
1227        return Err(EwfError::Malformed(format!("{label} has odd UTF-16 size")));
1228    }
1229
1230    let units: Vec<u16> = data
1231        .chunks_exact(2)
1232        .map(|chunk| u16::from_le_bytes(chunk.try_into().expect("chunk size checked")))
1233        .collect();
1234    let mut text = String::from_utf16(&units)
1235        .map_err(|_| EwfError::Malformed(format!("{label} is not valid UTF-16LE")))?;
1236    if text.ends_with('\0') {
1237        text.pop();
1238    }
1239    if text.is_empty() {
1240        Ok(None)
1241    } else {
1242        Ok(Some(text))
1243    }
1244}
1245
1246fn parse_hex_byte_stream(value: &str, label: &str) -> Result<Vec<u8>> {
1247    let hex: String = value
1248        .chars()
1249        .filter(|character| !character.is_whitespace())
1250        .collect();
1251    if !hex.len().is_multiple_of(2) {
1252        return Err(EwfError::Malformed(format!(
1253            "EWF2 single files {label} has odd hexadecimal size"
1254        )));
1255    }
1256
1257    (0..hex.len())
1258        .step_by(2)
1259        .map(|index| {
1260            u8::from_str_radix(&hex[index..index + 2], 16).map_err(|_| {
1261                EwfError::Malformed(format!(
1262                    "invalid EWF2 single files {label} hexadecimal value"
1263                ))
1264            })
1265        })
1266        .collect()
1267}
1268
1269fn parse_serialized_base16_string(value: &str, label: &str) -> Result<Option<String>> {
1270    if !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
1271        return Err(EwfError::Malformed(format!(
1272            "invalid EWF2 single files {label} hexadecimal value"
1273        )));
1274    }
1275    if value.bytes().all(|byte| byte == b'0') {
1276        Ok(None)
1277    } else {
1278        Ok(Some(value.to_ascii_lowercase()))
1279    }
1280}
1281
1282fn parse_number_pair(line: &str, label: &str) -> Result<(u64, usize)> {
1283    let mut parts = line.split('\t');
1284    let first = parts
1285        .next()
1286        .ok_or_else(|| EwfError::Malformed(format!("{label} is missing first value")))?;
1287    let second = parts
1288        .next()
1289        .ok_or_else(|| EwfError::Malformed(format!("{label} is missing second value")))?;
1290    if parts.next().is_some() {
1291        return Err(EwfError::Malformed(format!("{label} has too many values")));
1292    }
1293    let first = parse_u64(first, label)?;
1294    let second = parse_u64(second, label)?;
1295    let second = usize::try_from(second)
1296        .map_err(|_| EwfError::Malformed(format!("{label} does not fit usize")))?;
1297    Ok((first, second))
1298}
1299
1300fn parse_hex_usize(value: &str, label: &str) -> Result<usize> {
1301    let value = parse_hex_u64(value, label)?;
1302    usize::try_from(value)
1303        .map_err(|_| EwfError::Malformed(format!("EWF2 single files {label} out of bounds")))
1304}
1305
1306fn parse_hex_u64(value: &str, label: &str) -> Result<u64> {
1307    u64::from_str_radix(value, 16)
1308        .map_err(|_| EwfError::Malformed(format!("invalid EWF2 single files {label} value")))
1309}
1310
1311fn parse_u64(value: &str, label: &str) -> Result<u64> {
1312    value
1313        .parse()
1314        .map_err(|_| EwfError::Malformed(format!("invalid EWF2 single files {label} value")))
1315}
1316
1317fn parse_u32(value: &str, label: &str) -> Result<u32> {
1318    parse_u64(value, label).and_then(|value| {
1319        u32::try_from(value)
1320            .map_err(|_| EwfError::Malformed(format!("EWF2 single files {label} out of bounds")))
1321    })
1322}
1323
1324fn parse_i64(value: &str, label: &str) -> Result<i64> {
1325    value
1326        .parse()
1327        .map_err(|_| EwfError::Malformed(format!("invalid EWF2 single files {label} value")))
1328}
1329
1330fn parse_i32(value: &str, label: &str) -> Result<i32> {
1331    parse_i64(value, label).and_then(|value| {
1332        i32::try_from(value)
1333            .map_err(|_| EwfError::Malformed(format!("EWF2 single files {label} out of bounds")))
1334    })
1335}
1336
1337fn parse_non_negative_i32(value: &str, label: &str) -> Result<i32> {
1338    let value = parse_i32(value, label)?;
1339    if value < 0 {
1340        return Err(EwfError::Malformed(format!(
1341            "EWF2 single files {label} out of bounds"
1342        )));
1343    }
1344    Ok(value)
1345}