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 jiff::Timestamp;
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) -> Timestamp {
56 let unix_100ns = filetime.saturating_sub(FILETIME_EPOCH_OFFSET);
57 let unix_nanos = i128::from(unix_100ns) * 100;
58 Timestamp::from_nanosecond(unix_nanos).unwrap_or(Timestamp::UNIX_EPOCH)
59}
60
61pub fn ole_date_to_datetime(v: f64) -> Timestamp {
66 const OLE_TO_UNIX_DAYS: f64 = 25569.0;
67 if !v.is_finite() {
68 return Timestamp::UNIX_EPOCH;
69 }
70 let unix_secs_f64 = (v - OLE_TO_UNIX_DAYS) * 86400.0;
71 let unix_secs = unix_secs_f64 as i64;
72 let nanos = ((unix_secs_f64 - unix_secs as f64).abs() * 1_000_000_000.0) as u32;
73 Timestamp::new(unix_secs, nanos as i32).unwrap_or(Timestamp::UNIX_EPOCH)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn filetime_to_datetime_unix_epoch() {
82 let dt = filetime_to_datetime(FILETIME_EPOCH_OFFSET);
83 assert_eq!(dt.as_second(), 0, "must map to Unix epoch");
84 }
85
86 #[test]
87 fn filetime_to_datetime_known_date() {
88 let filetime = FILETIME_EPOCH_OFFSET + 1_718_438_400u64 * 10_000_000;
90 let dt = filetime_to_datetime(filetime);
91 assert_eq!(dt.as_second(), 1_718_438_400);
92 }
93
94 #[test]
95 fn filetime_to_datetime_sub_second() {
96 let dt = filetime_to_datetime(FILETIME_EPOCH_OFFSET + 12_345);
100 assert_eq!(dt.as_second(), 0);
101 assert_eq!(dt.as_nanosecond(), 1_234_500_i128);
102 }
103
104 #[test]
105 fn ole_date_to_datetime_unix_epoch() {
106 let dt = ole_date_to_datetime(25569.0);
108 assert_eq!(dt.as_second(), 0, "OLE day 25569 == Unix epoch");
109 }
110
111 #[test]
112 fn ole_date_to_datetime_known_date() {
113 let dt = ole_date_to_datetime(45000.0);
118 assert_eq!(dt.as_second(), 1_678_838_400);
119 }
120
121 #[test]
122 fn ole_date_to_datetime_non_finite_clamps_to_epoch() {
123 assert_eq!(ole_date_to_datetime(f64::NAN).as_second(), 0);
124 assert_eq!(ole_date_to_datetime(f64::INFINITY).as_second(), 0);
125 }
126
127 #[test]
128 fn record_size_constants_are_32() {
129 assert_eq!(NETWORK_RECORD_SIZE, 32usize);
130 assert_eq!(APP_RECORD_SIZE, 32usize);
131 }
132
133 #[test]
134 fn network_record_has_bytes_sent() {
135 let r = NetworkUsageRecord {
136 bytes_sent: 1024,
137 bytes_recv: 0,
138 timestamp: Timestamp::UNIX_EPOCH,
139 app_id: 1,
140 user_id: 0,
141 auto_inc_id: 0,
142 };
143 assert_eq!(r.bytes_sent, 1024);
144 }
145
146 #[test]
147 fn network_record_has_bytes_recv() {
148 let r = NetworkUsageRecord {
149 bytes_sent: 0,
150 bytes_recv: 2048,
151 timestamp: Timestamp::UNIX_EPOCH,
152 app_id: 1,
153 user_id: 0,
154 auto_inc_id: 0,
155 };
156 assert_eq!(r.bytes_recv, 2048);
157 }
158
159 #[test]
160 fn network_record_has_timestamp() {
161 let ts = Timestamp::UNIX_EPOCH;
162 let r = NetworkUsageRecord {
163 bytes_sent: 0,
164 bytes_recv: 0,
165 timestamp: ts,
166 app_id: 1,
167 user_id: 0,
168 auto_inc_id: 0,
169 };
170 let _ = r.timestamp;
171 }
172
173 #[test]
174 fn network_record_has_app_id() {
175 let r = NetworkUsageRecord {
176 bytes_sent: 0,
177 bytes_recv: 0,
178 timestamp: Timestamp::UNIX_EPOCH,
179 app_id: 42,
180 user_id: 0,
181 auto_inc_id: 0,
182 };
183 assert_eq!(r.app_id, 42_i32);
184 }
185
186 #[test]
187 fn app_usage_record_has_foreground_cycles() {
188 let r = AppUsageRecord {
189 app_id: 1,
190 user_id: 0,
191 timestamp: Timestamp::UNIX_EPOCH,
192 foreground_cycles: 999_000,
193 background_cycles: 0,
194 auto_inc_id: 0,
195 };
196 assert_eq!(r.foreground_cycles, 999_000_u64);
197 }
198
199 #[test]
200 fn id_map_entry_has_id_and_name() {
201 let e = IdMapEntry {
202 id: 7,
203 name: "explorer.exe".to_owned(),
204 };
205 assert_eq!(e.id, 7_i32);
206 assert_eq!(e.name, "explorer.exe");
207 }
208
209 #[test]
210 fn network_record_serializes_to_json() {
211 let r = NetworkUsageRecord {
212 bytes_sent: 512,
213 bytes_recv: 1024,
214 timestamp: Timestamp::UNIX_EPOCH,
215 app_id: 3,
216 user_id: 1,
217 auto_inc_id: 0,
218 };
219 let json = serde_json::to_string(&r).expect("serialise to JSON");
220 assert!(json.contains("bytes_sent"));
221 assert!(json.contains("512"));
222 assert!(!json.contains("auto_inc_id"));
224 }
225}