crates/signal-schema/tests/hardware_wire.rs
85 lines · 3741 bytes
1//! # The Swift↔Rust hardware wire contract (v4)
2//!
3//! The macOS IOKit collector is a sibling Swift package (`macos-collector/`)
4//! that speaks this crate's wire format. Swift and Rust are two independent
5//! implementations of the same byte layout, so the contract needs a test that
6//! pins the *exact bytes* both sides must agree on — not just a Rust round trip.
7//!
8//! This test hand-encodes, per the documented frame layout
9//! (`signal_schema::wire`), the canonical hardware frame the Swift collector
10//! emits, and asserts:
11//! 1. `wire::decode` parses those exact bytes into the expected [`Signal`]
12//! (this is signald's parse path — signald decodes hardware frames with the
13//! same function), and
14//! 2. `wire::encode` reproduces those exact bytes from the [`Signal`].
15//!
16//! The identical byte array is asserted on the Swift side in
17//! `macos-collector/Tests/CollectorCoreTests/WireTests.swift`, so both languages
18//! independently commit to the same wire. Cross-*process* execution of the Swift
19//! binary is not required to prove agreement — the shared literal is the proof.
20//!
21//! Privacy note: a hardware frame carries the same `f64`-only payload as every
22//! other signal. `CpuLoad` is untagged (`tag_present == 0`); there is no content
23//! field. The privacy invariant is intact for hardware signals by construction.
24
25use signal_schema::{wire, Signal, SignalName, Source, Value, SCHEMA_VERSION};
26
27/// The canonical hardware test vector, shared verbatim with the Swift side.
28///
29/// Signal: schema_version=4, ts=0, source=Hardware(3), name=CpuLoad(10),
30/// value=0.5 (`f64`), tag=None.
31///
32/// Frame bytes (little-endian throughout):
33/// ```text
34/// 15 00 00 00 body_len = 21 (u32)
35/// 04 00 schema_version = 4 (u16)
36/// 00 00 00 00 00 00 00 00 ts = 0 (u64)
37/// 03 source = Hardware (u8)
38/// 0A name = CpuLoad = 10 (u8)
39/// 00 00 00 00 00 00 E0 3F value = 0.5 (f64)
40/// 00 tag_present = 0 (u8)
41/// ```
42const CANONICAL_FRAME: [u8; 25] = [
43 0x15, 0x00, 0x00, 0x00, // body_len = 21
44 0x04, 0x00, // schema_version = 4
45 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ts = 0
46 0x03, // source = Hardware
47 0x0A, // name = CpuLoad (10)
48 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F, // value = 0.5
49 0x00, // tag_present = 0
50];
51
52fn canonical_signal() -> Signal {
53 Signal {
54 schema_version: SCHEMA_VERSION,
55 ts: 0,
56 source: Source::Hardware,
57 name: SignalName::CpuLoad,
58 value: Value(0.5),
59 tag: None,
60 }
61}
62
63/// signald parses a hardware frame produced to the documented byte layout.
64#[test]
65fn signald_decodes_the_swift_hardware_frame() {
66 let decoded = wire::decode(&CANONICAL_FRAME).expect("signald must decode the hardware frame");
67 assert_eq!(decoded, canonical_signal());
68 // The privacy boundary holds for hardware signals too: f64 payload, no tag.
69 assert_eq!(decoded.value, Value(0.5));
70 assert!(decoded.tag.is_none(), "aggregate hardware scalar carries no tag");
71}
72
73/// The Rust encoder reproduces the exact bytes the Swift encoder commits to.
74#[test]
75fn rust_encoder_matches_the_shared_byte_vector() {
76 assert_eq!(wire::encode(&canonical_signal()), CANONICAL_FRAME.to_vec());
77}
78
79/// The aggregate CPU metric is untagged and frozen at discriminant 10.
80#[test]
81fn cpu_load_is_untagged_and_stable() {
82 assert_eq!(SignalName::CpuLoad.to_u8(), 10);
83 assert_eq!(SignalName::from_u8(10), Some(SignalName::CpuLoad));
84 assert!(!SignalName::CpuLoad.allows_tag(), "aggregate CPU load is never tagged");
85}