px_native/cipher/sensor.rs
1//! `vP` — top-level sensor encryptor. Chains the six primitives in the
2//! captured init.js (line 6050) into the single output the runtime
3//! POSTs to `/<tenant>/xhr/b/s`:
4//!
5//! ```text
6//! events_json ── jw(·, IS=50) ── h_p ── v_q(secret_feed, ·, offsets) ── payload
7//! ▲
8//! │
9//! v_n(secret_feed.len(), payload_b64.len(), cu)
10//! ▲
11//! │
12//! v_l(pf) (= jw(b64(pf), VJ=10))
13//! ```
14
15use px_errors::AppError;
16
17use crate::cipher::b64::h_p;
18use crate::cipher::offsets::v_n;
19use crate::cipher::secret::v_l;
20use crate::cipher::splice::v_q;
21use crate::cipher::xor::{IS, jw};
22
23/// Encrypt a fully serialised sensor event batch.
24///
25/// * `events_json` — UTF-8 bytes of `JSON.stringify(events)` output
26/// (`hY` in the JS reference is JSON.stringify for our input shapes).
27/// * `pf` — `pf()` page-fingerprint bytes (or the tenant fallback when
28/// the runtime would have used `gC(365)`).
29/// * `cu` — the `ctx.cu` salt (the `vK` field).
30pub fn encrypt_sensor(events_json: &[u8], pf: &[u8], cu: &[u8]) -> Result<Vec<u8>, AppError> {
31 let secret_feed = v_l(pf);
32 let encrypted = h_p(&jw(events_json, IS)).into_bytes();
33 let offsets = v_n(secret_feed.len(), encrypted.len(), cu);
34 v_q(&secret_feed, &encrypted, &offsets)
35}
36
37#[cfg(test)]
38#[allow(clippy::expect_used)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn deterministic_for_same_inputs() {
44 let a = encrypt_sensor(b"[]", b"pedidosya.com.ar", b"cu-1").expect("encrypt");
45 let b = encrypt_sensor(b"[]", b"pedidosya.com.ar", b"cu-1").expect("encrypt");
46 assert_eq!(a, b);
47 }
48
49 #[test]
50 fn output_differs_for_distinct_pf() {
51 let a = encrypt_sensor(b"[]", b"pf-A", b"cu-shared").expect("encrypt");
52 let b = encrypt_sensor(b"[]", b"pf-B", b"cu-shared").expect("encrypt");
53 assert_ne!(a, b);
54 }
55}