px_native/events/batch.rs
1//! Compose a default event batch from a `SyntheticIdentity`.
2//!
3//! The PX-tag → data-field mapping is the part of the sensor protocol
4//! we have NOT fully reversed yet (ADR-0024 N3 follow-up). What this
5//! module does is emit a small batch matching the **shapes** we have
6//! seen in the deobfuscated runtime — enough for the encryptor to
7//! produce bytes the wire will accept the format of, even if a
8//! production-grade trust score requires more events to be added
9//! once we capture ground truth.
10
11use crate::events::identity::SyntheticIdentity;
12use crate::events::model::SensorEvent;
13
14/// Build a baseline event batch with the well-known PX-tag set we have
15/// surfaced from the eT15wiaE init.js. Each event carries the data
16/// fields we can synthesise locally.
17pub fn default_batch(identity: &SyntheticIdentity, now_ms: u64) -> Vec<SensorEvent> {
18 vec![
19 // Page-load / first emit. The runtime always sends one of these
20 // at boot, before any user interaction has happened.
21 SensorEvent::new("PX561")
22 .with("AzNweUZUfEs=", now_ms)
23 .with("EwNgCVZlZDw=", identity.user_agent.as_str())
24 .with("HCgvIllLKRA=", identity.locale.as_str()),
25 // Pixel-counter / visit-stats event. Carries the session count
26 // and a derived visit-age timestamp.
27 SensorEvent::new("PX11978")
28 .with("AzNweUVSfU0=", i64::from(identity.session_count))
29 .with(
30 "XGhvYhkOb1g=",
31 visit_age_ms(now_ms, identity.first_visit_days_ago),
32 ),
33 // Fingerprint container. We don't yet populate full WebGL /
34 // canvas vectors here; that's the next iteration. The shape
35 // is enough for the encryptor.
36 SensorEvent::new("PX12457").with("dWFGKzACQxw=", identity.timezone.as_str()),
37 ]
38}
39
40fn visit_age_ms(now_ms: u64, days_ago: u32) -> i64 {
41 const MS_PER_DAY: i64 = 86_400_000;
42 (now_ms as i64).saturating_sub(MS_PER_DAY.saturating_mul(i64::from(days_ago)))
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn default_batch_has_three_events() {
51 let id = SyntheticIdentity::test_default();
52 let batch = default_batch(&id, 1_716_192_345_678);
53 assert_eq!(batch.len(), 3);
54 assert_eq!(batch[0].t, "PX561");
55 assert!(batch[0].d.contains_key("AzNweUZUfEs="));
56 }
57
58 #[test]
59 fn visit_age_is_before_now() {
60 let now: u64 = 1_716_192_345_678;
61 let age = visit_age_ms(now, 14);
62 assert!((age as u64) < now);
63 }
64}