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(
130 path: &std::path::Path,
131) -> anyhow::Result<Vec<srum_core::EnergyLtRecord>> {
132 let db = ese_core::EseDatabase::open(path)?;
133 collect_table(&db, TABLE_ENERGY_USAGE_LT, energy_lt::decode_energy_lt_record)
134}
135
136pub fn parse_push_notifications(
145 path: &std::path::Path,
146) -> anyhow::Result<Vec<srum_core::PushNotificationRecord>> {
147 let db = ese_core::EseDatabase::open(path)?;
148 collect_table(
149 &db,
150 TABLE_PUSH_NOTIFICATIONS,
151 push_notification::decode_push_notification_record,
152 )
153}
154
155pub fn parse_app_timeline(
166 path: &std::path::Path,
167) -> anyhow::Result<Vec<srum_core::AppTimelineRecord>> {
168 let db = ese_core::EseDatabase::open(path)?;
169 collect_table(&db, TABLE_APP_TIMELINE, app_timeline::decode_app_timeline_record)
170}
171
172pub fn parse_id_map(path: &std::path::Path) -> anyhow::Result<Vec<srum_core::IdMapEntry>> {
180 let db = ese_core::EseDatabase::open(path)?;
181 collect_table(&db, TABLE_ID_MAP, |data, page, tag| {
182 id_map::decode_id_map_entry(data, page, tag)
183 })
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use tempfile::NamedTempFile;
190
191 #[test]
192 fn nonexistent_file_energy_lt_returns_err() {
193 let result = parse_energy_lt(std::path::Path::new("/nonexistent/SRUDB.dat"));
194 assert!(result.is_err());
195 }
196
197 #[test]
198 fn empty_file_energy_lt_returns_err() {
199 let tmp = NamedTempFile::new().expect("tempfile");
200 let result = parse_energy_lt(tmp.path());
201 assert!(result.is_err(), "empty file must return Err");
202 }
203
204 #[test]
205 fn energy_lt_result_type() {
206 let _: anyhow::Result<Vec<srum_core::EnergyLtRecord>> =
207 parse_energy_lt(std::path::Path::new("/nonexistent/SRUDB.dat"));
208 }
209
210 #[test]
211 fn nonexistent_file_app_timeline_returns_err() {
212 let result = parse_app_timeline(std::path::Path::new("/nonexistent/SRUDB.dat"));
213 assert!(result.is_err());
214 }
215
216 #[test]
217 fn empty_file_app_timeline_returns_err() {
218 let tmp = NamedTempFile::new().expect("tempfile");
219 let result = parse_app_timeline(tmp.path());
220 assert!(result.is_err(), "empty file must return Err");
221 }
222
223 #[test]
224 fn app_timeline_result_type() {
225 let _: anyhow::Result<Vec<srum_core::AppTimelineRecord>> =
226 parse_app_timeline(std::path::Path::new("/nonexistent/SRUDB.dat"));
227 }
228
229 #[test]
230 fn nonexistent_file_network_returns_err() {
231 let result = parse_network_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
232 assert!(result.is_err());
233 }
234
235 #[test]
236 fn nonexistent_file_app_returns_err() {
237 let result = parse_app_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
238 assert!(result.is_err());
239 }
240
241 #[test]
242 fn empty_file_network_returns_err() {
243 let tmp = NamedTempFile::new().expect("tempfile");
244 let result = parse_network_usage(tmp.path());
245 assert!(result.is_err(), "empty file must return Err");
246 }
247
248 #[test]
249 fn empty_file_app_returns_err() {
250 let tmp = NamedTempFile::new().expect("tempfile");
251 let result = parse_app_usage(tmp.path());
252 assert!(result.is_err(), "empty file must return Err");
253 }
254
255 #[test]
256 fn network_usage_result_type() {
257 let _: anyhow::Result<Vec<srum_core::NetworkUsageRecord>> =
258 parse_network_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
259 }
260
261 #[test]
262 fn app_usage_result_type() {
263 let _: anyhow::Result<Vec<srum_core::AppUsageRecord>> =
264 parse_app_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
265 }
266}