1use forensicnomicon::report::{Finding, Observation, Provenance, Report, Source, TimelineEvent};
6
7use crate::DiskReport;
8
9fn findings_of<'a, O: Observation + 'a>(
13 anomalies: impl IntoIterator<Item = &'a O>,
14 analyzer: &str,
15 scope: &str,
16) -> Vec<Finding> {
17 anomalies
18 .into_iter()
19 .map(|an| {
20 an.to_finding(Source {
21 analyzer: analyzer.to_string(),
22 scope: scope.to_string(),
23 version: None,
24 })
25 })
26 .collect()
27}
28
29#[must_use]
40pub fn mbr_findings(a: &mbr_partition_forensic::MbrAnalysis) -> Vec<Finding> {
41 findings_of(&a.anomalies, "mbr-partition-forensic", "MBR")
42}
43
44#[must_use]
46pub fn gpt_findings(a: &gpt_partition_forensic::GptAnalysis) -> Vec<Finding> {
47 findings_of(&a.anomalies, "gpt-partition-forensic", "GPT")
48}
49
50#[must_use]
52pub fn apm_findings(a: &apm_partition_forensic::ApmAnalysis) -> Vec<Finding> {
53 findings_of(&a.anomalies, "apm-partition-forensic", "APM")
54}
55
56#[must_use]
58pub fn mbr_provenance(a: &mbr_partition_forensic::MbrAnalysis) -> Vec<Provenance> {
59 vec![
60 Provenance {
61 label: "boot code".to_string(),
62 value: format!("{:?}", a.boot_code_id),
63 source: "mbr-partition-forensic".to_string(),
64 },
65 Provenance {
66 label: "partitioning era".to_string(),
67 value: format!("{:?}", a.era),
68 source: "mbr-partition-forensic".to_string(),
69 },
70 Provenance {
71 label: "disk signature".to_string(),
72 value: format!("{:#010x}", a.disk_serial),
73 source: "mbr-partition-forensic".to_string(),
74 },
75 ]
76}
77
78#[must_use]
80pub fn gpt_provenance(a: &gpt_partition_forensic::GptAnalysis) -> Vec<Provenance> {
81 vec![
82 Provenance {
83 label: "disk GUID".to_string(),
84 value: a.disk_guid.to_string(),
85 source: "gpt-partition-forensic".to_string(),
86 },
87 Provenance {
88 label: "sector size".to_string(),
89 value: format!("{} bytes", a.sector_size),
90 source: "gpt-partition-forensic".to_string(),
91 },
92 Provenance {
93 label: "GPT SHA-256".to_string(),
94 value: a.gpt_sha256.clone(),
95 source: "gpt-partition-forensic".to_string(),
96 },
97 ]
98}
99
100#[must_use]
102pub fn apm_provenance(a: &apm_partition_forensic::ApmAnalysis) -> Vec<Provenance> {
103 vec![
104 Provenance {
105 label: "block size".to_string(),
106 value: format!("{} bytes", a.block_size),
107 source: "apm-partition-forensic".to_string(),
108 },
109 Provenance {
110 label: "device blocks".to_string(),
111 value: a.device_block_count.to_string(),
112 source: "apm-partition-forensic".to_string(),
113 },
114 ]
115}
116
117#[must_use]
121pub fn iso_findings(a: &iso9660_forensic::IsoAnalysis) -> Vec<Finding> {
122 findings_of(&a.anomalies, "iso9660-forensic", "ISO 9660")
123}
124
125#[must_use]
129pub fn iso_provenance(a: &iso9660_forensic::IsoAnalysis) -> Vec<Provenance> {
130 let v = &a.volume;
131 let mut entries: Vec<(&str, String)> = vec![
132 ("volume label", v.volume_label.clone()),
133 ("system identifier", v.system_id.clone()),
134 ("volume set", v.volume_set_id.clone()),
135 ("publisher", v.publisher_id.clone()),
136 ("data preparer", v.data_preparer_id.clone()),
137 ("application", v.application_id.clone()),
138 ("sector mode", v.sector_mode.clone()),
139 (
140 "extensions",
141 format!("Rock Ridge: {}, Joliet: {}", v.has_rock_ridge, v.has_joliet),
142 ),
143 ("sessions", v.session_count.to_string()),
144 ];
145 if v.has_enhanced_volume_descriptor {
146 entries.push(("enhanced volume descriptor", "present".to_string()));
147 }
148 if !v.rock_ridge_uids.is_empty() || !v.rock_ridge_gids.is_empty() {
149 entries.push((
150 "Rock Ridge owners",
151 format!("uids {:?}, gids {:?}", v.rock_ridge_uids, v.rock_ridge_gids),
152 ));
153 }
154 if !v.boot_entries.is_empty() {
155 let platforms: Vec<&str> = v.boot_entries.iter().map(|b| b.platform.as_str()).collect();
156 entries.push((
157 "El Torito boot",
158 format!("{} entries ({})", v.boot_entries.len(), platforms.join(", ")),
159 ));
160 }
161 entries
162 .into_iter()
163 .filter(|(_, value)| !value.is_empty())
164 .map(|(label, value)| Provenance {
165 label: label.to_string(),
166 value,
167 source: "iso9660-forensic".to_string(),
168 })
169 .collect()
170}
171
172#[must_use]
175pub fn iso_timeline(a: &iso9660_forensic::IsoAnalysis) -> Vec<TimelineEvent> {
176 let v = &a.volume;
177 [
178 (&v.creation_time, "ISO 9660 volume created"),
179 (&v.modification_time, "ISO 9660 volume last modified"),
180 (
181 &v.earliest_file_time,
182 "earliest file recorded time (authoring window start)",
183 ),
184 (
185 &v.latest_file_time,
186 "latest file recorded time (authoring window end)",
187 ),
188 ]
189 .into_iter()
190 .filter_map(|(when, event)| {
191 when.as_ref().map(|w| TimelineEvent {
192 when: Some(w.clone()),
193 source: "iso9660-forensic".to_string(),
194 event: event.to_string(),
195 })
196 })
197 .collect()
198}
199
200#[must_use]
202pub fn iso_report(a: &iso9660_forensic::IsoAnalysis) -> Report {
203 let mut out = Report::default();
204 out.findings = iso_findings(a);
205 out.provenance = iso_provenance(a);
206 out.timeline = iso_timeline(a);
207 out
208}
209
210#[must_use]
213pub fn report(disk: &DiskReport) -> Report {
214 let (findings, provenance) = match disk {
215 DiskReport::Apm(a) => (apm_findings(a), apm_provenance(a)),
216 DiskReport::Mbr(m) => (mbr_findings(m), mbr_provenance(m)),
217 DiskReport::Gpt(m) => {
218 let mut findings = mbr_findings(m);
219 let mut provenance = mbr_provenance(m);
220 if let Some(gpt) = &m.gpt {
221 findings.extend(gpt_findings(gpt));
222 provenance.extend(gpt_provenance(gpt));
223 }
224 (findings, provenance)
225 }
226 };
227 let mut out = Report::default();
228 out.findings = findings;
229 out.provenance = provenance;
230 out
231}