mod app_timeline;
mod app_usage;
mod connectivity;
mod energy;
mod energy_lt;
mod id_map;
mod network;
mod push_notification;
use ese_core::EseError;
use forensicnomicon::srum::{
TABLE_APP_RESOURCE_USAGE, TABLE_APP_TIMELINE, TABLE_ENERGY_USAGE, TABLE_ENERGY_USAGE_LT,
TABLE_ID_MAP, TABLE_NETWORK_CONNECTIVITY, TABLE_NETWORK_USAGE, TABLE_PUSH_NOTIFICATIONS,
};
#[derive(Debug, thiserror::Error)]
pub enum SrumError {
#[error("ese: {0}")]
Ese(#[from] EseError),
#[error("page {page} tag {tag}: {detail}")]
DecodeError {
page: u32,
tag: usize,
detail: String,
},
}
fn collect_table<T>(
db: &ese_core::EseDatabase,
table: &str,
decode: impl Fn(&[u8], u32, usize) -> Result<T, SrumError>,
) -> anyhow::Result<Vec<T>> {
let cursor = match db.table_records(table) {
Ok(c) => c,
Err(EseError::TableNotFound { .. }) => return Ok(vec![]),
Err(e) => return Err(e.into()),
};
let records = cursor
.filter_map(|r| match r {
Ok((page, tag, data)) => decode(&data, page, tag).ok(),
Err(_) => None,
})
.collect();
Ok(records)
}
pub fn parse_network_usage(
path: &std::path::Path,
) -> anyhow::Result<Vec<srum_core::NetworkUsageRecord>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(&db, TABLE_NETWORK_USAGE, network::decode_network_record)
}
pub fn parse_app_usage(path: &std::path::Path) -> anyhow::Result<Vec<srum_core::AppUsageRecord>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(&db, TABLE_APP_RESOURCE_USAGE, app_usage::decode_app_record)
}
pub fn parse_network_connectivity(
path: &std::path::Path,
) -> anyhow::Result<Vec<srum_core::NetworkConnectivityRecord>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(
&db,
TABLE_NETWORK_CONNECTIVITY,
connectivity::decode_connectivity_record,
)
}
pub fn parse_energy_usage(
path: &std::path::Path,
) -> anyhow::Result<Vec<srum_core::EnergyUsageRecord>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(&db, TABLE_ENERGY_USAGE, energy::decode_energy_record)
}
pub fn parse_energy_lt(path: &std::path::Path) -> anyhow::Result<Vec<srum_core::EnergyLtRecord>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(
&db,
TABLE_ENERGY_USAGE_LT,
energy_lt::decode_energy_lt_record,
)
}
pub fn parse_push_notifications(
path: &std::path::Path,
) -> anyhow::Result<Vec<srum_core::PushNotificationRecord>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(
&db,
TABLE_PUSH_NOTIFICATIONS,
push_notification::decode_push_notification_record,
)
}
pub fn parse_app_timeline(
path: &std::path::Path,
) -> anyhow::Result<Vec<srum_core::AppTimelineRecord>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(
&db,
TABLE_APP_TIMELINE,
app_timeline::decode_app_timeline_record,
)
}
pub fn parse_id_map(path: &std::path::Path) -> anyhow::Result<Vec<srum_core::IdMapEntry>> {
let db = ese_core::EseDatabase::open(path)?;
collect_table(&db, TABLE_ID_MAP, |data, page, tag| {
id_map::decode_id_map_entry(data, page, tag)
})
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn nonexistent_file_energy_lt_returns_err() {
let result = parse_energy_lt(std::path::Path::new("/nonexistent/SRUDB.dat"));
assert!(result.is_err());
}
#[test]
fn empty_file_energy_lt_returns_err() {
let tmp = NamedTempFile::new().expect("tempfile");
let result = parse_energy_lt(tmp.path());
assert!(result.is_err(), "empty file must return Err");
}
#[test]
fn energy_lt_result_type() {
let _: anyhow::Result<Vec<srum_core::EnergyLtRecord>> =
parse_energy_lt(std::path::Path::new("/nonexistent/SRUDB.dat"));
}
#[test]
fn nonexistent_file_app_timeline_returns_err() {
let result = parse_app_timeline(std::path::Path::new("/nonexistent/SRUDB.dat"));
assert!(result.is_err());
}
#[test]
fn empty_file_app_timeline_returns_err() {
let tmp = NamedTempFile::new().expect("tempfile");
let result = parse_app_timeline(tmp.path());
assert!(result.is_err(), "empty file must return Err");
}
#[test]
fn app_timeline_result_type() {
let _: anyhow::Result<Vec<srum_core::AppTimelineRecord>> =
parse_app_timeline(std::path::Path::new("/nonexistent/SRUDB.dat"));
}
#[test]
fn nonexistent_file_network_returns_err() {
let result = parse_network_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
assert!(result.is_err());
}
#[test]
fn nonexistent_file_app_returns_err() {
let result = parse_app_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
assert!(result.is_err());
}
#[test]
fn empty_file_network_returns_err() {
let tmp = NamedTempFile::new().expect("tempfile");
let result = parse_network_usage(tmp.path());
assert!(result.is_err(), "empty file must return Err");
}
#[test]
fn empty_file_app_returns_err() {
let tmp = NamedTempFile::new().expect("tempfile");
let result = parse_app_usage(tmp.path());
assert!(result.is_err(), "empty file must return Err");
}
#[test]
fn network_usage_result_type() {
let _: anyhow::Result<Vec<srum_core::NetworkUsageRecord>> =
parse_network_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
}
#[test]
fn app_usage_result_type() {
let _: anyhow::Result<Vec<srum_core::AppUsageRecord>> =
parse_app_usage(std::path::Path::new("/nonexistent/SRUDB.dat"));
}
}