1mod app_timeline;
8mod app_usage;
9mod connectivity;
10mod energy;
11mod energy_lt;
12mod id_map;
13mod network;
14mod push_notification;
15
16use ese_core::EseError;
17use forensicnomicon::srum::{
18 TABLE_APP_RESOURCE_USAGE, TABLE_APP_TIMELINE, TABLE_ENERGY_USAGE, TABLE_ENERGY_USAGE_LT,
19 TABLE_ID_MAP, TABLE_NETWORK_CONNECTIVITY, TABLE_NETWORK_USAGE, TABLE_PUSH_NOTIFICATIONS,
20};
21
22#[derive(Debug, thiserror::Error)]
24pub enum SrumError {
25 #[error("ese: {0}")]
26 Ese(#[from] EseError),
27 #[error("page {page} tag {tag}: {detail}")]
28 DecodeError {
29 page: u32,
30 tag: usize,
31 detail: String,
32 },
33}
34
35fn collect_table<T>(
41 db: &ese_core::EseDatabase,
42 table: &str,
43 decode: impl Fn(&[u8], u32, usize) -> Result<T, SrumError>,
44) -> anyhow::Result<Vec<T>> {
45 let cursor = match db.table_records(table) {
46 Ok(c) => c,
47 Err(EseError::TableNotFound { .. }) => return Ok(vec![]),
48 Err(e) => return Err(e.into()),
49 };
50 let records = cursor
51 .filter_map(|r| match r {
52 Ok((page, tag, data)) => decode(&data, page, tag).ok(),
53 Err(_) => None,
54 })
55 .collect();
56 Ok(records)
57}
58
59pub fn parse_network_usage(
68 path: &std::path::Path,
69) -> anyhow::Result<Vec<srum_core::NetworkUsageRecord>> {
70 let db = ese_core::EseDatabase::open(path)?;
71 collect_table(&db, TABLE_NETWORK_USAGE, network::decode_network_record)
72}
73
74pub fn parse_app_usage(path: &std::path::Path) -> anyhow::Result<Vec<srum_core::AppUsageRecord>> {
83 let db = ese_core::EseDatabase::open(path)?;
84 collect_table(&db, TABLE_APP_RESOURCE_USAGE, app_usage::decode_app_record)
85}
86
87pub fn parse_network_connectivity(
96 path: &std::path::Path,
97) -> anyhow::Result<Vec<srum_core::NetworkConnectivityRecord>> {
98 let db = ese_core::EseDatabase::open(path)?;
99 collect_table(
100 &db,
101 TABLE_NETWORK_CONNECTIVITY,
102 connectivity::decode_connectivity_record,
103 )
104}
105
106pub fn parse_energy_usage(
115 path: &std::path::Path,
116) -> anyhow::Result<Vec<srum_core::EnergyUsageRecord>> {
117 let db = ese_core::EseDatabase::open(path)?;
118 collect_table(&db, TABLE_ENERGY_USAGE, energy::decode_energy_record)
119}
120
121pub fn parse_energy_lt(path: &std::path::Path) -> anyhow::Result<Vec<srum_core::EnergyLtRecord>> {
130 let db = ese_core::EseDatabase::open(path)?;
131 collect_table(
132 &db,
133 TABLE_ENERGY_USAGE_LT,
134 energy_lt::decode_energy_lt_record,
135 )
136}
137
138pub fn parse_push_notifications(
147 path: &std::path::Path,
148) -> anyhow::Result<Vec<srum_core::PushNotificationRecord>> {
149 let db = ese_core::EseDatabase::open(path)?;
150 collect_table(
151 &db,
152 TABLE_PUSH_NOTIFICATIONS,
153 push_notification::decode_push_notification_record,
154 )
155}
156
157pub fn parse_app_timeline(
168 path: &std::path::Path,
169) -> anyhow::Result<Vec<srum_core::AppTimelineRecord>> {
170 let db = ese_core::EseDatabase::open(path)?;
171 collect_table(
172 &db,
173 TABLE_APP_TIMELINE,
174 app_timeline::decode_app_timeline_record,
175 )
176}
177
178pub fn parse_id_map(path: &std::path::Path) -> anyhow::Result<Vec<srum_core::IdMapEntry>> {
186 let db = ese_core::EseDatabase::open(path)?;
187 collect_table(&db, TABLE_ID_MAP, |data, page, tag| {
188 id_map::decode_id_map_entry(data, page, tag)
189 })
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use tempfile::NamedTempFile;
196
197 #[test]
198 fn nonexistent_file_energy_lt_returns_err() {
199 let result = parse_energy_lt(std::path::Path::new("/nonexistent/SRUDB.dat"));
200 assert!(result.is_err());
201 }
202
203 #[test]
204 fn empty_file_energy_lt_returns_err() {
205 let tmp = NamedTempFile::new().expect("tempfile");
206 let result = parse_energy_lt(tmp.path());
207 assert!(result.is_err(), "empty file must return Err");
208 }
209
210 #[test]
211 fn energy_lt_result_type() {
212 let _: anyhow::Result<Vec<srum_core::EnergyLtRecord>> =
213 parse_energy_lt(std::path::Path::new("/nonexistent/SRUDB.dat"));
214 }
215
216 #[test]
217 fn nonexistent_file_app_timeline_returns_err() {
218 let result = parse_app_timeline(std::path::Path::new("/nonexistent/SRUDB.dat"));
219 assert!(result.is_err());
220 }
221
222 #[test]
223 fn empty_file_app_timeline_returns_err() {
224 let tmp = NamedTempFile::new().expect("tempfile");
225 let result = parse_app_timeline(tmp.path());
226 assert!(result.is_err(), "empty file must return Err");
227 }
228
229 #[test]
230 fn app_timeline_result_type() {
231 let _: anyhow::Result<Vec<srum_core::AppTimelineRecord>> =
232 parse_app_timeline(std::path::Path::new("/nonexistent/SRUDB.dat"));
233 }
234
235 #[test]
236 fn nonexistent_file_network_returns_err() {
237 let result = parse_network_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
238 assert!(result.is_err());
239 }
240
241 #[test]
242 fn nonexistent_file_app_returns_err() {
243 let result = parse_app_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
244 assert!(result.is_err());
245 }
246
247 #[test]
248 fn empty_file_network_returns_err() {
249 let tmp = NamedTempFile::new().expect("tempfile");
250 let result = parse_network_usage(tmp.path());
251 assert!(result.is_err(), "empty file must return Err");
252 }
253
254 #[test]
255 fn empty_file_app_returns_err() {
256 let tmp = NamedTempFile::new().expect("tempfile");
257 let result = parse_app_usage(tmp.path());
258 assert!(result.is_err(), "empty file must return Err");
259 }
260
261 #[test]
262 fn network_usage_result_type() {
263 let _: anyhow::Result<Vec<srum_core::NetworkUsageRecord>> =
264 parse_network_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
265 }
266
267 #[test]
268 fn app_usage_result_type() {
269 let _: anyhow::Result<Vec<srum_core::AppUsageRecord>> =
270 parse_app_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
271 }
272}