1pub mod app_timeline;
7pub mod app_usage;
8pub mod connectivity;
9pub mod energy;
10pub mod energy_lt;
11pub mod id_map;
12pub mod network;
13pub mod push_notification;
14
15pub use app_timeline::AppTimelineRecord;
16pub use app_usage::AppUsageRecord;
17pub use connectivity::NetworkConnectivityRecord;
18pub use energy::EnergyUsageRecord;
19pub use energy_lt::EnergyLtRecord;
20pub use id_map::IdMapEntry;
21pub use network::NetworkUsageRecord;
22pub use push_notification::PushNotificationRecord;
23
24use chrono::{DateTime, Utc};
25
26pub const FILETIME_EPOCH_OFFSET: u64 = 116_444_736_000_000_000;
29
30pub const NETWORK_RECORD_SIZE: usize = 32;
32
33pub const APP_RECORD_SIZE: usize = 32;
35
36pub const APP_TIMELINE_RECORD_SIZE: usize = 32;
38
39pub const NETWORK_CONNECTIVITY_RECORD_SIZE: usize = 28;
41
42pub const ENERGY_RECORD_SIZE: usize = 32;
44
45pub const PUSH_NOTIFICATION_RECORD_SIZE: usize = 24;
47
48pub const ID_MAP_MIN_SIZE: usize = 6;
50
51pub fn filetime_to_datetime(filetime: u64) -> DateTime<Utc> {
56 let unix_100ns = filetime.saturating_sub(FILETIME_EPOCH_OFFSET);
57 let secs = i64::try_from(unix_100ns / 10_000_000).unwrap_or(i64::MAX);
58 let nanos = u32::try_from((unix_100ns % 10_000_000) * 100).unwrap_or(0);
59 DateTime::from_timestamp(secs, nanos).unwrap_or(DateTime::UNIX_EPOCH.with_timezone(&Utc))
60}
61
62pub fn ole_date_to_datetime(v: f64) -> DateTime<Utc> {
67 const OLE_TO_UNIX_DAYS: f64 = 25569.0;
68 if !v.is_finite() {
69 return DateTime::UNIX_EPOCH.with_timezone(&Utc);
70 }
71 let unix_secs_f64 = (v - OLE_TO_UNIX_DAYS) * 86400.0;
72 let unix_secs = unix_secs_f64 as i64;
73 let nanos = ((unix_secs_f64 - unix_secs as f64).abs() * 1_000_000_000.0) as u32;
74 DateTime::from_timestamp(unix_secs, nanos).unwrap_or(DateTime::UNIX_EPOCH.with_timezone(&Utc))
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn filetime_to_datetime_unix_epoch() {
83 let dt = filetime_to_datetime(FILETIME_EPOCH_OFFSET);
84 assert_eq!(dt.timestamp(), 0, "must map to Unix epoch");
85 }
86
87 #[test]
88 fn filetime_to_datetime_known_date() {
89 let filetime = FILETIME_EPOCH_OFFSET + 1_718_438_400u64 * 10_000_000;
91 let dt = filetime_to_datetime(filetime);
92 assert_eq!(dt.timestamp(), 1_718_438_400);
93 }
94
95 #[test]
96 fn record_size_constants_are_32() {
97 assert_eq!(NETWORK_RECORD_SIZE, 32usize);
98 assert_eq!(APP_RECORD_SIZE, 32usize);
99 }
100
101 #[test]
102 fn network_record_has_bytes_sent() {
103 let r = NetworkUsageRecord {
104 bytes_sent: 1024,
105 bytes_recv: 0,
106 timestamp: chrono::DateTime::UNIX_EPOCH.with_timezone(&chrono::Utc),
107 app_id: 1,
108 user_id: 0,
109 auto_inc_id: 0,
110 };
111 assert_eq!(r.bytes_sent, 1024);
112 }
113
114 #[test]
115 fn network_record_has_bytes_recv() {
116 let r = NetworkUsageRecord {
117 bytes_sent: 0,
118 bytes_recv: 2048,
119 timestamp: chrono::DateTime::UNIX_EPOCH.with_timezone(&chrono::Utc),
120 app_id: 1,
121 user_id: 0,
122 auto_inc_id: 0,
123 };
124 assert_eq!(r.bytes_recv, 2048);
125 }
126
127 #[test]
128 fn network_record_has_timestamp() {
129 let ts = chrono::DateTime::UNIX_EPOCH.with_timezone(&chrono::Utc);
130 let r = NetworkUsageRecord {
131 bytes_sent: 0,
132 bytes_recv: 0,
133 timestamp: ts,
134 app_id: 1,
135 user_id: 0,
136 auto_inc_id: 0,
137 };
138 let _ = r.timestamp;
139 }
140
141 #[test]
142 fn network_record_has_app_id() {
143 let r = NetworkUsageRecord {
144 bytes_sent: 0,
145 bytes_recv: 0,
146 timestamp: chrono::DateTime::UNIX_EPOCH.with_timezone(&chrono::Utc),
147 app_id: 42,
148 user_id: 0,
149 auto_inc_id: 0,
150 };
151 assert_eq!(r.app_id, 42_i32);
152 }
153
154 #[test]
155 fn app_usage_record_has_foreground_cycles() {
156 let r = AppUsageRecord {
157 app_id: 1,
158 user_id: 0,
159 timestamp: chrono::DateTime::UNIX_EPOCH.with_timezone(&chrono::Utc),
160 foreground_cycles: 999_000,
161 background_cycles: 0,
162 auto_inc_id: 0,
163 };
164 assert_eq!(r.foreground_cycles, 999_000_u64);
165 }
166
167 #[test]
168 fn id_map_entry_has_id_and_name() {
169 let e = IdMapEntry {
170 id: 7,
171 name: "explorer.exe".to_owned(),
172 };
173 assert_eq!(e.id, 7_i32);
174 assert_eq!(e.name, "explorer.exe");
175 }
176
177 #[test]
178 fn network_record_serializes_to_json() {
179 let r = NetworkUsageRecord {
180 bytes_sent: 512,
181 bytes_recv: 1024,
182 timestamp: chrono::DateTime::UNIX_EPOCH.with_timezone(&chrono::Utc),
183 app_id: 3,
184 user_id: 1,
185 auto_inc_id: 0,
186 };
187 let json = serde_json::to_string(&r).expect("serialise to JSON");
188 assert!(json.contains("bytes_sent"));
189 assert!(json.contains("512"));
190 assert!(!json.contains("auto_inc_id"));
192 }
193}