From 139ca7397fffe5502aea2e1300e7c8a5ccf76328 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 00:17:50 +0200 Subject: [PATCH] muninn-contracts: pin every record's wire bytes and every defaulted key One table, 15 records: each encodes to its pinned positional MessagePack array (dropped, swapped, retyped keys change the bytes), and every record with a defaulted tail decodes from a payload that ends before each defaulted key, one truncation per key, reading the rest as zero values. Replaces the two hand-written hid and hue tests and the single hue truncation. Co-Authored-By: Claude Sonnet 5.5 --- crates/muninn-contracts/src/lib.rs | 465 ++++++++++++++++++++++++----- 1 file changed, 398 insertions(+), 67 deletions(-) diff --git a/crates/muninn-contracts/src/lib.rs b/crates/muninn-contracts/src/lib.rs index fb312ef..e111ae5 100644 --- a/crates/muninn-contracts/src/lib.rs +++ b/crates/muninn-contracts/src/lib.rs @@ -118,75 +118,406 @@ mod tests { } } + /// One wire pin per record. `bytes` is the record's positional MessagePack + /// array, encoded from a sample whose every field holds a distinct, + /// non-default value (Options: one `None`, one `Some`). `first_default` is + /// the key of the first defaulted tail field; records without one carry + /// `None`. + struct Pin { + record: &'static str, + bytes: Vec, + first_default: Option, + golden: &'static str, + /// Decode a payload as this record and encode it again. + reencode: fn(&[u8]) -> Result, String>, + } + + fn pin( + record: &'static str, + sample: T, + first_default: Option, + golden: &'static str, + ) -> Pin + where + T: serde::Serialize + serde::de::DeserializeOwned, + { + Pin { + record, + bytes: rmp_serde::to_vec(&sample).unwrap(), + first_default, + golden, + reencode: |bytes| { + let decoded: T = rmp_serde::from_slice(bytes).map_err(|error| error.to_string())?; + rmp_serde::to_vec(&decoded).map_err(|error| error.to_string()) + }, + } + } + + fn text(value: &str) -> String { + value.to_string() + } + + fn texts(values: &[&str]) -> Vec { + values.iter().map(|value| value.to_string()).collect() + } + + fn pins() -> Vec { + vec![ + pin( + "MuninnTelemetrySurfaceRecord", + MuninnTelemetrySurfaceRecord { + surface_id: text("surface"), + host_id: text("host"), + state: text("live"), + available_sources: texts(&["src"]), + stream_affordances: texts(&["aff"]), + active_streams: texts(&["act"]), + activation_authority: text("auth"), + detail: text("detail"), + updated_at: text("t"), + primary_stream_id: text("psid"), + primary_stream_label: text("plabel"), + command_rudp_target: text("cmd"), + media_target_host: text("mth"), + media_port: 4001, + media_packet_bytes: 1200, + rudp_video_bitrate_kbps: 8000, + rudp_latency_budget_ms: 40, + video_source_ids: texts(&["vid"]), + video_source_labels: texts(&["vlab"]), + audio_source_ids: texts(&["aid"]), + audio_source_labels: texts(&["alab"]), + }, + Some(9), + "dc0015a773757266616365a4686f7374a46c69766591a373726391a361666691a3616374a461757468a664657461696ca174a470736964a6706c6162656ca3636d64a36d7468cd0fa1cd04b0cd1f402891a376696491a4766c616291a361696491a4616c6162", + ), + pin( + "MuninnCaptureStreamRecord", + MuninnCaptureStreamRecord { + stream_id: text("stream"), + host_id: text("host"), + state: text("live"), + video_source: text("vsrc"), + audio_source: text("asrc"), + transport: text("rudp"), + targets: texts(&["tgt"]), + command_witness: text("wit"), + supervisor_pid: None, + mux_pid: Some(77), + restart_count: 3, + detail: text("detail"), + updated_at: text("t"), + }, + None, + "9da673747265616da4686f7374a46c697665a476737263a461737263a47275647091a3746774a3776974c04d03a664657461696ca174", + ), + pin( + "MuninnCaptureStreamCommandRecord", + MuninnCaptureStreamCommandRecord { + command_id: text("cmd"), + host_id: text("host"), + stream_id: text("stream"), + state: text("live"), + action: text("start"), + target_host: text("tgt"), + port: 4001, + obs_target_host: Some(text("obs")), + obs_port: 4002, + media_transport: text("rudp"), + media_packet_bytes: 1200, + requested_by: text("who"), + detail: text("detail"), + updated_at: text("t"), + rudp_video_bitrate_kbps: 8000, + rudp_latency_budget_ms: 40, + video_source_id: text("vid"), + audio_source_id: text("aid"), + }, + Some(14), + "dc0012a3636d64a4686f7374a673747265616da46c697665a57374617274a3746774cd0fa1a36f6273cd0fa2a472756470cd04b0a377686fa664657461696ca174cd1f4028a3766964a3616964", + ), + pin( + "MuninnObsStreamCatalogRecord", + MuninnObsStreamCatalogRecord { + catalog_id: text("catalog"), + host_id: text("host"), + stream_ids: texts(&["sid"]), + labels: texts(&["lab"]), + urls: texts(&["url"]), + states: texts(&["st"]), + updated_at: text("t"), + command_rudp_target: text("cmd"), + media_target_host: text("mth"), + media_port: 4001, + media_packet_bytes: 1200, + rudp_video_bitrate_kbps: 8000, + rudp_latency_budget_ms: 40, + video_source_ids: texts(&["vid"]), + video_source_labels: texts(&["vlab"]), + audio_source_ids: texts(&["aid"]), + audio_source_labels: texts(&["alab"]), + }, + Some(7), + "dc0011a7636174616c6f67a4686f737491a373696491a36c616291a375726c91a27374a174a3636d64a36d7468cd0fa1cd04b0cd1f402891a376696491a4766c616291a361696491a4616c6162", + ), + pin( + "MuninnMoveMarkerCandidateRecord", + MuninnMoveMarkerCandidateRecord { + stream_id: text("stream"), + host_id: text("host"), + camera_id: text("cam"), + frame_sequence: 11, + source_id_hash: 12, + tile_x: 13, + tile_y: 14, + center_x_px: 1.5, + center_y_px: 2.5, + radius_px: 3.5, + area_px: 15, + mean_luma: 4.5, + peak_luma: 16, + score: 0.5, + observed_at: text("t"), + move_id: text("move"), + }, + Some(15), + "dc0010a673747265616da4686f7374a363616d0b0c0d0eca3fc00000ca40200000ca406000000fca4090000010ca3f000000a174a46d6f7665", + ), + pin( + "MuninnMoveControllerStateRecord", + MuninnMoveControllerStateRecord { + stream_id: text("stream"), + host_id: text("host"), + move_id: text("move"), + sequence: 21, + source_timestamp_ns: -22, + accelerometer_xyz: vec![1.0, 2.0, 3.0], + gyroscope_xyz: vec![4.0, 5.0, 6.0], + magnetometer_xyz: vec![7.0, 8.0, 9.0], + trigger_value: 0.25, + buttons: texts(&["cross"]), + battery01: 0.75, + observed_at: text("t"), + source_path: text("/dev/js0"), + }, + Some(12), + "9da673747265616da4686f7374a46d6f766515ea93ca3f800000ca40000000ca4040000093ca40800000ca40a00000ca40c0000093ca40e00000ca41000000ca41100000ca3e80000091a563726f7373ca3f400000a174a82f6465762f6a7330", + ), + pin( + "MuninnHidControllerStateRecord", + MuninnHidControllerStateRecord { + stream_id: text("muninn.raven.hid"), + host_id: text("raven"), + device_id: text("xbox-0"), + device_kind: text("xinput"), + sequence: 7, + source_timestamp_ns: -3, + axes: vec![0.5, -1.0], + buttons: texts(&["a", "b"]), + battery01: 1.0, + observed_at: text("unix:100"), + source_path: text("xinput://0"), + }, + None, + "9bb06d756e696e6e2e726176656e2e686964a5726176656ea678626f782d30a678696e70757407fd92ca3f000000cabf80000092a161a162ca3f800000a8756e69783a313030aa78696e7075743a2f2f30", + ), + pin( + "MuninnMoveIdentityRecord", + MuninnMoveIdentityRecord { + identity_id: text("identity"), + host_id: text("host"), + move_id: text("move"), + source_path: text("path"), + bluetooth_host_address: text("bt"), + state: text("live"), + detail: text("detail"), + observed_at: text("t"), + }, + None, + "98a86964656e74697479a4686f7374a46d6f7665a470617468a26274a46c697665a664657461696ca174", + ), + pin( + "MuninnMoveLightCommandRecord", + MuninnMoveLightCommandRecord { + command_id: text("cmd"), + host_id: text("host"), + move_id: text("move"), + hidraw_path: text("hidraw"), + colors: texts(&["red"]), + durations_ms: vec![100, 200], + repeat_count: 5, + authority: text("auth"), + state: text("live"), + detail: text("detail"), + updated_at: text("t"), + }, + None, + "9ba3636d64a4686f7374a46d6f7665a668696472617791a37265649264ccc805a461757468a46c697665a664657461696ca174", + ), + pin( + "MuninnMoveHueProgramRecord", + MuninnMoveHueProgramRecord { + program_id: text("prog"), + host_id: text("raven"), + mode: text("cycle"), + cycle_ms: 20, + epoch_ns: -1, + hold_at_ns: 0, + requested_by: text("mimir"), + updated_at: text("unix:1"), + order_mode: text("ring"), + transition_percent: 50, + transition_percent_explicit: true, + }, + Some(8), + "9ba470726f67a5726176656ea56379636c6514ff00a56d696d6972a6756e69783a31a472696e6732c3", + ), + pin( + "MuninnMoveTrackerHealthRecord", + MuninnMoveTrackerHealthRecord { + health_id: text("health"), + host_id: text("host"), + camera_id: text("cam"), + camera_index: -2, + state: text("live"), + camera_name: text("name"), + camera_api: text("api"), + width: 640, + height: 480, + exposure: 0.5, + calibrated_controller_count: 2, + update_count: 30, + observation_count: 31, + latest_observation_count: 32, + last_observation_at: text("last"), + detail: text("detail"), + updated_at: text("t"), + image_mean_rgb: vec![1, 2, 3], + image_peak_rgb: vec![4, 5, 6], + color_evidence_move_ids: texts(&["move"]), + color_evidence_pixel_counts: vec![40], + rejected_stale_count: 41, + rejected_radius_count: 42, + rejected_bounds_count: 43, + rejected_continuity_count: 44, + }, + Some(17), + "dc0019a66865616c7468a4686f7374a363616dfea46c697665a46e616d65a3617069cd0280cd01e0ca3f000000021e1f20a46c617374a664657461696ca174930102039304050691a46d6f76659128292a2b2c", + ), + pin( + "MuninnMoveEvidenceTransportHealthRecord", + MuninnMoveEvidenceTransportHealthRecord { + health_id: text("health"), + host_id: text("host"), + stream_id: text("stream"), + produced_frames: 50, + local_ring_admissions: 51, + remote_handoffs: 52, + remote_sends: 53, + updated_at: text("t"), + }, + None, + "98a66865616c7468a4686f7374a673747265616d32333435a174", + ), + pin( + "MuninnQuestAccessRecord", + MuninnQuestAccessRecord { + access_id: text("access"), + host_id: text("host"), + serial: text("serial"), + connection_state: text("conn"), + product: text("product"), + model: text("model"), + device: text("device"), + transport_id: text("tid"), + input_stream_id: text("input"), + pose_stream_id: text("pose"), + video_input_stream_id: text("video"), + video_input_transport: text("vtransport"), + state: text("live"), + detail: text("detail"), + observed_at: text("t"), + }, + None, + "9fa6616363657373a4686f7374a673657269616ca4636f6e6ea770726f64756374a56d6f64656ca6646576696365a3746964a5696e707574a4706f7365a5766964656faa767472616e73706f7274a46c697665a664657461696ca174", + ), + pin( + "MuninnCommandBoundaryCompatRecord", + MuninnCommandBoundaryCompatRecord { + value: serde_json::json!({ "daemon_id": "muninn" }), + }, + None, + "9181a96461656d6f6e5f6964a66d756e696e6e", + ), + pin( + "MuninnTransportProfileCompatRecord", + MuninnTransportProfileCompatRecord { + value: serde_json::json!({ "state": "live" }), + }, + None, + "9181a57374617465a46c697665", + ), + ] + } + + /// Every record's bytes are exactly the pinned positional array: a swapped + /// key, a changed type or a moved field changes them. #[test] - fn hid_controller_state_wire_layout_is_positional_by_key() { - let record = MuninnHidControllerStateRecord { - stream_id: "muninn.raven.hid".into(), - host_id: "raven".into(), - device_id: "xbox-0".into(), - device_kind: "xinput".into(), - sequence: 7, - source_timestamp_ns: -3, - axes: vec![0.5, -1.0], - buttons: vec!["a".into(), "b".into()], - battery01: 1.0, - observed_at: "unix:100".into(), - source_path: "xinput://0".into(), + fn every_record_encodes_to_its_pinned_positional_bytes() { + let mut wrong = Vec::new(); + for pin in pins() { + let actual = hex(&pin.bytes); + if actual != pin.golden { + wrong.push(format!("{}: {actual}", pin.record)); + } + let reencoded = (pin.reencode)(&pin.bytes) + .unwrap_or_else(|error| panic!("{} does not decode its own bytes: {error}", pin.record)); + assert_eq!(reencoded, pin.bytes, "{} round trip", pin.record); + } + assert!( + wrong.is_empty(), + "unpinned or moved wire layouts:\n{}", + wrong.join("\n") + ); + } + + /// A payload written before a defaulted key existed still decodes, once + /// per defaulted key, and every key from there on reads as its zero value. + #[test] + fn every_defaulted_tail_key_decodes_from_a_payload_that_ends_before_it() { + use serde_json::Value; + let is_zero = |value: &Value| match value { + Value::Null => true, + Value::Bool(flag) => !flag, + Value::Number(number) => number.as_f64() == Some(0.0), + Value::String(text) => text.is_empty(), + Value::Array(items) => items.is_empty(), + Value::Object(_) => false, }; - let bytes = rmp_serde::to_vec(&record).unwrap(); - assert_eq!( - hex(&bytes), - "9bb06d756e696e6e2e726176656e2e686964a5726176656ea678626f782d30a678696e70757407fd92ca3f000000cabf80000092a161a162ca3f800000a8756e69783a313030aa78696e7075743a2f2f30" - ); - assert_eq!( - rmp_serde::from_slice::(&bytes).unwrap(), - record - ); - } - - #[test] - fn hue_program_wire_layout_carries_its_defaulted_tail() { - let record = MuninnMoveHueProgramRecord { - program_id: "prog".into(), - host_id: "raven".into(), - mode: "cycle".into(), - cycle_ms: 20, - epoch_ns: -1, - hold_at_ns: 0, - requested_by: "mimir".into(), - updated_at: "unix:1".into(), - order_mode: "ring".into(), - transition_percent: 50, - transition_percent_explicit: true, - }; - let bytes = rmp_serde::to_vec(&record).unwrap(); - assert_eq!( - hex(&bytes), - "9ba470726f67a5726176656ea56379636c6514ff00a56d696d6972a6756e69783a31a472696e6732c3" - ); - assert_eq!( - rmp_serde::from_slice::(&bytes).unwrap(), - record - ); - } - - #[test] - fn hue_program_from_an_older_payload_defaults_its_tail() { - let older = decode_hex( - "98a470726f67a5726176656ea56379636c6514ff00a56d696d6972a6756e69783a31", - ); - let record: MuninnMoveHueProgramRecord = rmp_serde::from_slice(&older).unwrap(); - assert_eq!(record.updated_at, "unix:1"); - assert_eq!(record.order_mode, ""); - assert_eq!(record.transition_percent, 0); - assert!(!record.transition_percent_explicit); - } - - fn decode_hex(text: &str) -> Vec { - (0..text.len()) - .step_by(2) - .map(|index| u8::from_str_radix(&text[index..index + 2], 16).unwrap()) - .collect() + for pin in pins() { + let Some(first_default) = pin.first_default else { + continue; + }; + let full: Vec = rmp_serde::from_slice(&pin.bytes).unwrap(); + for end in first_default..full.len() { + let truncated = rmp_serde::to_vec(&full[..end]).unwrap(); + let decoded = (pin.reencode)(&truncated).unwrap_or_else(|error| { + panic!( + "{} does not decode a payload ending before key {end}: {error}", + pin.record + ) + }); + let decoded: Vec = rmp_serde::from_slice(&decoded).unwrap(); + assert_eq!(decoded.len(), full.len(), "{} ending before key {end}", pin.record); + assert_eq!(&decoded[..end], &full[..end], "{} ending before key {end}", pin.record); + assert!( + decoded[end..].iter().all(is_zero), + "{} ending before key {end} defaulted to {:?}", + pin.record, + &decoded[end..] + ); + } + } } fn hex(bytes: &[u8]) -> String {