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!(
159 "{} entries ({})",
160 v.boot_entries.len(),
161 platforms.join(", ")
162 ),
163 ));
164 }
165 entries
166 .into_iter()
167 .filter(|(_, value)| !value.is_empty())
168 .map(|(label, value)| Provenance {
169 label: label.to_string(),
170 value,
171 source: "iso9660-forensic".to_string(),
172 })
173 .collect()
174}
175
176#[must_use]
179pub fn iso_timeline(a: &iso9660_forensic::IsoAnalysis) -> Vec<TimelineEvent> {
180 let v = &a.volume;
181 [
182 (&v.creation_time, "ISO 9660 volume created"),
183 (&v.modification_time, "ISO 9660 volume last modified"),
184 (
185 &v.earliest_file_time,
186 "earliest file recorded time (authoring window start)",
187 ),
188 (
189 &v.latest_file_time,
190 "latest file recorded time (authoring window end)",
191 ),
192 ]
193 .into_iter()
194 .filter_map(|(when, event)| {
195 when.as_ref().map(|w| TimelineEvent {
196 when: Some(w.clone()),
197 source: "iso9660-forensic".to_string(),
198 event: event.to_string(),
199 })
200 })
201 .collect()
202}
203
204#[must_use]
206pub fn iso_report(a: &iso9660_forensic::IsoAnalysis) -> Report {
207 let mut out = Report::default();
208 out.findings = iso_findings(a);
209 out.provenance = iso_provenance(a);
210 out.timeline = iso_timeline(a);
211 out
212}
213
214#[must_use]
217pub fn report(disk: &DiskReport) -> Report {
218 let (findings, provenance) = match disk {
219 DiskReport::Apm(a) => (apm_findings(a), apm_provenance(a)),
220 DiskReport::Mbr(m) => (mbr_findings(m), mbr_provenance(m)),
221 DiskReport::Gpt(m) => {
222 let mut findings = mbr_findings(m);
223 let mut provenance = mbr_provenance(m);
224 if let Some(gpt) = &m.gpt {
225 findings.extend(gpt_findings(gpt));
226 provenance.extend(gpt_provenance(gpt));
227 }
228 (findings, provenance)
229 }
230 };
231 let mut out = Report::default();
232 out.findings = findings;
233 out.provenance = provenance;
234 out
235}