Skip to main content

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}