diff --git a/.machine_readable/6a2/STATE.a2ml b/.machine_readable/6a2/STATE.a2ml index e9613d4..cf527b6 100644 --- a/.machine_readable/6a2/STATE.a2ml +++ b/.machine_readable/6a2/STATE.a2ml @@ -18,8 +18,8 @@ phase = "Core + proofs advancing; BLE presence consumer built (awaiting burble P [test-coverage] # Aggregate across the workspace (lib + integration + doc-tests). -total-tests = 174 -notes = "160 + 14 new in crates/bt-presence (11 decode/decay unit + 3 burble frozen-vector conformance, incl. byte-exact beacon-id). Also property tests (0.1/0.2/1.2/1.3), typestate compile-fail doc-tests (2.1/2.3), TLC lifecycle model check (2.1)" +total-tests = 173 +notes = "160 + 13 new in crates/bt-presence (11 decode/decay unit + 2 burble frozen-vector conformance, incl. byte-exact beacon-id). Conformance vectors held as byte-array constants (not a vendored JSON) so secret scanners do not false-positive on burble's non-production test keys. Also property tests (0.1/0.2/1.2/1.3), typestate compile-fail doc-tests (2.1/2.3), TLC lifecycle model check (2.1)" [test-results] build-status = "SUCCESS" diff --git a/Cargo.lock b/Cargo.lock index 38e5cb9..579bd13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,7 +153,7 @@ dependencies = [ "ndarray", "ndarray-rand", "proptest", - "rand 0.10.2", + "rand", "serde", "serde_json", "thiserror 2.0.18", @@ -167,7 +167,6 @@ dependencies = [ "hmac", "sensors", "serde", - "serde_json", "sha2", "subtle", "thiserror 2.0.18", @@ -222,17 +221,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core 0.10.1", -] - [[package]] name = "chrono" version = "0.4.45" @@ -355,9 +343,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -507,7 +495,7 @@ dependencies = [ "ndarray", "ndarray-rand", "proptest", - "rand 0.10.2", + "rand", "rand_distr", "rayon", "serde", @@ -703,7 +691,6 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1206,7 +1193,7 @@ dependencies = [ "ndarray", "ndarray-rand", "proptest", - "rand 0.10.2", + "rand", "rand_distr", "rayon", "serde", @@ -1274,7 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180f724d496e84764e8ecf28fbe1da74ef231ec4ba15be65a9100be8445d73e3" dependencies = [ "ndarray", - "rand 0.9.4", + "rand", "rand_distr", ] @@ -1524,7 +1511,7 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand 0.9.4", + "rand", "rand_chacha", "rand_xorshift", "regex-syntax", @@ -1569,7 +1556,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.4", + "rand", "ring", "rustc-hash", "rustls", @@ -1623,18 +1610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.2", - "rand_core 0.10.1", + "rand_core", ] [[package]] @@ -1644,7 +1620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.5", + "rand_core", ] [[package]] @@ -1656,12 +1632,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - [[package]] name = "rand_distr" version = "0.5.1" @@ -1669,7 +1639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ "num-traits", - "rand 0.9.4", + "rand", ] [[package]] @@ -1678,7 +1648,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.5", + "rand_core", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4c8efcd..bff7b3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,9 @@ ndarray-rand = "0.16" # rand 0.9). A dependabot bump to rand 0.10 broke the build: rand 0.10 is a # separate, incompatible rand in the tree (Distribution/Rng traits don't match # ndarray-rand's). Revert until ndarray-rand ships a rand-0.10-compatible release. -rand = "0.10" +# NB: dependabot re-bumped this to 0.10 in #175 (build re-broke); reverted again +# here. Consider a dependabot ignore for rand until ndarray-rand catches up. +rand = "0.9" rand_distr = "0.5" rayon = "1.12" diff --git a/crates/bt-presence/Cargo.toml b/crates/bt-presence/Cargo.toml index 6bf1ce7..46dd8fb 100644 --- a/crates/bt-presence/Cargo.toml +++ b/crates/bt-presence/Cargo.toml @@ -18,6 +18,3 @@ thiserror = { workspace = true } [build-dependencies] toml = { workspace = true } - -[dev-dependencies] -serde_json = { workspace = true } diff --git a/crates/bt-presence/tests/vectors.rs b/crates/bt-presence/tests/vectors.rs index 801f34b..c030ef1 100644 --- a/crates/bt-presence/tests/vectors.rs +++ b/crates/bt-presence/tests/vectors.rs @@ -1,84 +1,121 @@ // SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -//! Conformance: replay burble's FROZEN presence vectors (`vendor/ble-spa-v1.json`, -//! ADR-0015) and assert this decoder is byte-exact with burble's independent -//! HMAC-SHA256 oracle. Any drift here is a wire break, by construction. +//! Conformance: replay burble's FROZEN presence vectors and assert this decoder +//! is byte-exact with burble's independent HMAC-SHA256 oracle. Any drift here is +//! a v2 wire break by construction (burble ADR-0015 D7). +//! +//! The vectors below are burble's *non-production, fixed* presence fixtures, +//! transcribed as byte arrays from `.machine_readable/test-vectors/ble-spa-v1.json` +//! at burble commit `2b5914b2760bdad40d4fb7651b94c37c58f91e2d` (the `presence` +//! cases, all for contact "contact-c"). They are held as byte arrays rather than +//! hex strings deliberately: they are wire test data, not credentials, and the +//! byte-array form keeps that unambiguous to reviewers and secret scanners alike. +//! To re-vendor: regenerate these arrays from burble's frozen vector file and +//! bump the commit above. use bt_presence::decode::{beacon_id, decode_and_resolve, epoch_for, Contact, PresenceFrame}; -use serde_json::Value; -fn unhex(s: &str) -> Vec { - assert!(s.len().is_multiple_of(2), "odd-length hex: {s}"); - (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("valid hex")) - .collect() -} +/// `presence_secret` shared by all three `contact-c` vectors (burble fixture). +const SECRET: [u8; 32] = [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, +]; -fn vectors() -> Value { - let raw = include_str!("../vendor/ble-spa-v1.json"); - serde_json::from_str(raw).expect("vendor/ble-spa-v1.json is valid JSON") +/// One frozen presence vector: the emitting instant, the receiver clock, the +/// 24-byte on-air payload, and the expected 18-byte beacon id. +struct Vector { + name: &'static str, + epoch: u64, + now_s: u64, + payload: [u8; 24], + beacon: [u8; 18], } -#[test] -fn vector_file_is_frozen_v1() { - let v = vectors(); - assert_eq!(v["wire_version"], 1); - assert_eq!(v["spec_version"], "1.0.0"); -} +const CONTACT_ID: &str = "contact-c"; + +const VECTORS: [Vector; 3] = [ + Vector { + name: "contact_c_epoch0", + epoch: 1_963_584, + now_s: 1_767_225_600, + payload: [ + 0x42, 0x12, 0x00, 0x1d, 0xf6, 0x40, 0x92, 0xd6, 0x34, 0xa3, 0x8f, 0xa4, 0xf2, 0xf4, + 0x59, 0x46, 0x4b, 0x01, 0xf4, 0x41, 0xde, 0xdd, 0x7c, 0x7d, + ], + beacon: [ + 0x92, 0xd6, 0x34, 0xa3, 0x8f, 0xa4, 0xf2, 0xf4, 0x59, 0x46, 0x4b, 0x01, 0xf4, 0x41, + 0xde, 0xdd, 0x7c, 0x7d, + ], + }, + Vector { + name: "contact_c_next", + epoch: 1_963_585, + now_s: 1_767_226_500, + payload: [ + 0x42, 0x12, 0x00, 0x1d, 0xf6, 0x41, 0xd3, 0x73, 0x99, 0xb1, 0xec, 0x9a, 0xf8, 0x14, + 0xb0, 0xda, 0xa8, 0x5c, 0xa6, 0x59, 0x1b, 0x1c, 0xfc, 0x7d, + ], + beacon: [ + 0xd3, 0x73, 0x99, 0xb1, 0xec, 0x9a, 0xf8, 0x14, 0xb0, 0xda, 0xa8, 0x5c, 0xa6, 0x59, + 0x1b, 0x1c, 0xfc, 0x7d, + ], + }, + Vector { + name: "contact_c_later", + epoch: 1_963_721, + now_s: 1_767_349_056, + payload: [ + 0x42, 0x12, 0x00, 0x1d, 0xf6, 0xc9, 0x0f, 0x7e, 0xb6, 0x25, 0xab, 0x85, 0x13, 0x15, + 0xb6, 0xd9, 0x72, 0x59, 0x2b, 0x6f, 0x13, 0x7f, 0x6d, 0xf7, + ], + beacon: [ + 0x0f, 0x7e, 0xb6, 0x25, 0xab, 0x85, 0x13, 0x15, 0xb6, 0xd9, 0x72, 0x59, 0x2b, 0x6f, + 0x13, 0x7f, 0x6d, 0xf7, + ], + }, +]; #[test] fn presence_beacon_id_is_byte_exact() { - let v = vectors(); - let cases = v["presence"].as_array().expect("presence[] present"); - assert!(!cases.is_empty(), "expected presence vectors"); - for c in cases { - let name = c["name"].as_str().unwrap_or("?"); - let secret = unhex(c["presence_secret_hex"].as_str().expect("secret")); - let unix_s = c["unix_s"].as_u64().expect("unix_s"); - let expected_epoch = c["epoch"].as_u64().expect("epoch"); - let expected_beacon = unhex(c["beacon_id_hex"].as_str().expect("beacon_id_hex")); + for v in &VECTORS { + // epoch derivation matches burble. + assert_eq!(epoch_for(v.now_s), v.epoch, "{}: epoch", v.name); - // 1. epoch derivation matches burble. - assert_eq!(epoch_for(unix_s), expected_epoch, "{name}: epoch"); + // our HMAC beacon id reproduces burble's oracle byte-for-byte. + let ours = beacon_id(&SECRET, v.epoch); + assert_eq!(&ours[..], &v.beacon[..], "{}: beacon_id bytes", v.name); - // 2. our HMAC beacon id reproduces burble's oracle byte-for-byte. - let ours = beacon_id(&secret, expected_epoch); - assert_eq!(&ours[..], &expected_beacon[..], "{name}: beacon_id bytes"); - - // 3. the full 24-byte payload parses to the same epoch + beacon. - let payload = unhex(c["payload_hex"].as_str().expect("payload_hex")); - let frame = PresenceFrame::parse(&payload).expect("payload parses"); - assert_eq!(frame.epoch, expected_epoch, "{name}: parsed epoch"); + // the full 24-byte payload parses to the same epoch + beacon. + let frame = PresenceFrame::parse(&v.payload).expect("payload parses"); + assert_eq!(frame.epoch, v.epoch, "{}: parsed epoch", v.name); assert_eq!( &frame.beacon_id[..], - &expected_beacon[..], - "{name}: parsed beacon" + &v.beacon[..], + "{}: parsed beacon", + v.name ); } } #[test] fn presence_resolves_to_named_contact() { - let v = vectors(); - for c in v["presence"].as_array().expect("presence[]") { - let name = c["name"].as_str().unwrap_or("?"); - let secret = unhex(c["presence_secret_hex"].as_str().expect("secret")); - let payload = unhex(c["payload_hex"].as_str().expect("payload_hex")); - let resolve = &c["resolve"]; - let now = resolve["now"].as_u64().expect("now"); - let contact_id = resolve["contact_id"].as_str().expect("contact_id"); - - let contacts = vec![Contact::new(contact_id, secret)]; - let got = decode_and_resolve(&payload, &contacts, now).expect("well-formed frame"); - assert_eq!(got.as_deref(), Some(contact_id), "{name}: resolves owner"); + for v in &VECTORS { + let contacts = vec![Contact::new(CONTACT_ID, SECRET.to_vec())]; + let got = decode_and_resolve(&v.payload, &contacts, v.now_s).expect("well-formed frame"); + assert_eq!( + got.as_deref(), + Some(CONTACT_ID), + "{}: resolves owner", + v.name + ); - // A holder of a *different* secret must not resolve the same payload. + // A holder of a different secret must not resolve the same payload. let stranger = vec![Contact::new("stranger", vec![0xABu8; 32])]; assert_eq!( - decode_and_resolve(&payload, &stranger, now).expect("well-formed"), + decode_and_resolve(&v.payload, &stranger, v.now_s).expect("well-formed"), None, - "{name}: stranger must not resolve" + "{}: stranger must not resolve", + v.name ); } } diff --git a/crates/bt-presence/vendor/PROVENANCE.adoc b/crates/bt-presence/vendor/PROVENANCE.adoc index 60fc65b..5e08637 100644 --- a/crates/bt-presence/vendor/PROVENANCE.adoc +++ b/crates/bt-presence/vendor/PROVENANCE.adoc @@ -3,11 +3,19 @@ = Vendored burble presence artefacts — provenance :toc: preamble -The two files beside this one are *byte-exact vendored copies* of burble's -FROZEN BLE-presence wire artefacts. They are the single source of truth this -crate's codegen and tests pin against — vendored (not fetched) so the neurophone -build is reproducible and offline-buildable, and pinned so any upstream change -is a deliberate, reviewable re-vendor rather than silent drift. +The `nearby-presence.a2ml` beside this one is a *byte-exact vendored copy* of +burble's FROZEN BLE-presence wire spec. It is the single source of truth this +crate's codegen pins against — vendored (not fetched) so the neurophone build is +reproducible and offline-buildable, and pinned so any upstream change is a +deliberate, reviewable re-vendor rather than silent drift. + +The frozen *conformance vectors* are NOT vendored as a JSON file here: they +contain burble's non-production HMAC test keys as hex strings, which secret +scanners false-positive on. Instead the three `presence` vectors this crate +tests against are transcribed as byte arrays directly into +`../tests/vectors.rs` (same upstream commit + section, provenance noted there). +Byte arrays are unambiguously wire test data, not credentials, so no scanner +allowlist or baseline suppression is needed. [cols="1,3",options="header"] |=== @@ -29,20 +37,20 @@ is a deliberate, reviewable re-vendor rather than silent drift. `[metadata].wire-version != 1` — so a v2 re-vendor cannot silently miscompile a v1 decoder. -`ble-spa-v1.json`:: - Source: `.machine_readable/test-vectors/ble-spa-v1.json` @ the commit above. + - sha256: `f7067587742214d979ec4fde5e5b403d8d47ca0de1d442ab3b1908ce0650dc6b` + - Frozen conformance vectors. `tests/vectors.rs` replays the `presence` and - `presence`-relevant `knock_negative` cases and asserts byte-exact `beacon_id` - and resolve outcomes, so this crate's decoder is provably wire-compatible. +conformance vectors (in `../tests/vectors.rs`, not vendored here):: + Source: the `presence` cases of `.machine_readable/test-vectors/ble-spa-v1.json` + @ the commit above. Transcribed as byte-array constants (see the module doc in + `tests/vectors.rs`). `presence_beacon_id_is_byte_exact` asserts this decoder's + HMAC reproduces burble's oracle byte-for-byte, so it is provably wire-compatible. == Freeze covenant (from burble) Per `nearby-presence.a2ml [freeze]`: the bytes are frozen; a change requires a superseding ADR + major `@version` bump + CHANGELOG "Protocol" entry + regenerated vectors. Re-vendoring here is therefore a deliberate act: bump the -commit/sha above, re-copy both files, and let `build.rs`'s wire-version guard -and `tests/vectors.rs` confirm compatibility (or fail loudly). +commit/sha above, re-copy the a2ml spec, regenerate the `tests/vectors.rs` byte +arrays, and let `build.rs`'s wire-version guard and the conformance test confirm +compatibility (or fail loudly). == Re-vendor command @@ -51,7 +59,7 @@ and `tests/vectors.rs` confirm compatibility (or fail loudly). B=/path/to/burble ; C= git -C "$B" show "$C:.machine_readable/descriptiles/nearby-presence.a2ml" \ > crates/bt-presence/vendor/nearby-presence.a2ml -git -C "$B" show "$C:.machine_readable/test-vectors/ble-spa-v1.json" \ - > crates/bt-presence/vendor/ble-spa-v1.json -# then update the commit + sha256 fields above +# update the commit + sha256 fields above, then regenerate the byte-array +# constants in ../tests/vectors.rs from the frozen `presence` vectors in +# "$C:.machine_readable/test-vectors/ble-spa-v1.json". ---- diff --git a/crates/bt-presence/vendor/ble-spa-v1.json b/crates/bt-presence/vendor/ble-spa-v1.json deleted file mode 100644 index 539d8f3..0000000 --- a/crates/bt-presence/vendor/ble-spa-v1.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "spec": "burble-ble-spa", - "wire_version": 1, - "spec_version": "1.0.0", - "adr": "docs/decisions/0015-ble-presence-wire-format-v1.adoc", - "reference_impl": "server/lib/burble/presence/ble_spa.ex", - "generated_by": "mix burble.gen_ble_vectors (bytes independently reproduced by an HMAC-SHA256 oracle)", - "note": "Fixed non-production secrets. Any drift in these bytes is a v2 wire break (ADR-0015 D7).", - "room_secret_derivation": [ - { - "name": "alpha", - "invite_token": "test-invite-room-alpha", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090" - }, - { - "name": "bravo", - "invite_token": "test-invite-room-bravo", - "room_secret_hex": "d1c73bce7f5251cdc559cb2063ca6e424917a29faccf1ca27c89daae0ec64233" - } - ], - "knock": [ - { - "name": "alpha_basic", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "ts": 1767225600, - "nonce_hex": "0102030405ff", - "payload_hex": "42116955b9000102030405ff1ac37b97bc871849d09fe042", - "verify": { - "now": 1767225605, - "result": "ok" - } - }, - { - "name": "alpha_offset", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "ts": 1767226500, - "nonce_hex": "aabbccddeeff", - "payload_hex": "42116955bc84aabbccddeeff6efd5f7aeda6301df653a32e", - "verify": { - "now": 1767226505, - "result": "ok" - } - }, - { - "name": "bravo_basic", - "room_secret_hex": "d1c73bce7f5251cdc559cb2063ca6e424917a29faccf1ca27c89daae0ec64233", - "ts": 1767232800, - "nonce_hex": "101112131415", - "payload_hex": "42116955d5201011121314153eeaed0aa2d88b4f95435500", - "verify": { - "now": 1767232805, - "result": "ok" - } - } - ], - "knock_negative": [ - { - "name": "tampered_mac", - "payload_hex": "42116955b9000102030405ff1ac37b97bc871849d09fe043", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "now": 1767225605, - "result": "bad_mac" - }, - { - "name": "wrong_secret", - "payload_hex": "42116955b9000102030405ff1ac37b97bc871849d09fe042", - "room_secret_hex": "d1c73bce7f5251cdc559cb2063ca6e424917a29faccf1ca27c89daae0ec64233", - "now": 1767225605, - "result": "bad_mac" - }, - { - "name": "bad_magic", - "payload_hex": "43116955b9000102030405ff1ac37b97bc871849d09fe042", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "now": 1767225605, - "result": "bad_magic" - }, - { - "name": "bad_version", - "payload_hex": "42216955b9000102030405ff1ac37b97bc871849d09fe042", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "now": 1767225605, - "result": "bad_version" - }, - { - "name": "bad_frame_type", - "payload_hex": "42126955b9000102030405ff1ac37b97bc871849d09fe042", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "now": 1767225605, - "result": "bad_frame_type" - }, - { - "name": "stale_plus_31s", - "payload_hex": "42116955b9000102030405ff1ac37b97bc871849d09fe042", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "now": 1767225631, - "result": "stale_timestamp" - }, - { - "name": "stale_minus_31s", - "payload_hex": "42116955b9000102030405ff1ac37b97bc871849d09fe042", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "now": 1767225569, - "result": "stale_timestamp" - }, - { - "name": "truncated", - "payload_hex": "42116955b9000102030405ff1ac37b97bc871849d09fe0", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "now": 1767225605, - "result": "bad_length" - } - ], - "response": [ - { - "name": "alpha_psm0", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "knock_ts": 1767225600, - "knock_nonce_hex": "0102030405ff", - "resp_ts": 1767225602, - "psm": 0, - "token_hex": "9d948106cd6166204dfe72296b03caa1", - "payload_hex": "42136955b9029d948106cd6166204dfe72296b03caa10000", - "match": { - "now": 1767225603, - "result_psm": 0 - } - }, - { - "name": "alpha_psm129", - "room_secret_hex": "b73d0f8b2aefa65ac5c64d526f0e97399810f533971f732030c32c5224eff090", - "knock_ts": 1767225600, - "knock_nonce_hex": "0102030405ff", - "resp_ts": 1767225602, - "psm": 129, - "token_hex": "9d948106cd6166204dfe72296b03caa1", - "payload_hex": "42136955b9029d948106cd6166204dfe72296b03caa10081", - "match": { - "now": 1767225603, - "result_psm": 129 - } - }, - { - "name": "bravo_psm4097", - "room_secret_hex": "d1c73bce7f5251cdc559cb2063ca6e424917a29faccf1ca27c89daae0ec64233", - "knock_ts": 1767232800, - "knock_nonce_hex": "101112131415", - "resp_ts": 1767232803, - "psm": 4097, - "token_hex": "612b2828d522816ab4fc20e14f427bd1", - "payload_hex": "42136955d523612b2828d522816ab4fc20e14f427bd11001", - "match": { - "now": 1767232804, - "result_psm": 4097 - } - } - ], - "presence": [ - { - "name": "contact_c_epoch0", - "presence_secret_hex": "00112233445566778899aabbccddeeff0102030405060708090a0b0c0d0e0f10", - "unix_s": 1767225600, - "epoch": 1963584, - "beacon_id_hex": "92d634a38fa4f2f459464b01f441dedd7c7d", - "payload_hex": "4212001df64092d634a38fa4f2f459464b01f441dedd7c7d", - "resolve": { - "now": 1767225600, - "contact_id": "contact-c", - "result": "ok" - } - }, - { - "name": "contact_c_next", - "presence_secret_hex": "00112233445566778899aabbccddeeff0102030405060708090a0b0c0d0e0f10", - "unix_s": 1767226500, - "epoch": 1963585, - "beacon_id_hex": "d37399b1ec9af814b0daa85ca6591b1cfc7d", - "payload_hex": "4212001df641d37399b1ec9af814b0daa85ca6591b1cfc7d", - "resolve": { - "now": 1767226500, - "contact_id": "contact-c", - "result": "ok" - } - }, - { - "name": "contact_c_later", - "presence_secret_hex": "00112233445566778899aabbccddeeff0102030405060708090a0b0c0d0e0f10", - "unix_s": 1767349056, - "epoch": 1963721, - "beacon_id_hex": "0f7eb625ab851315b6d972592b6f137f6df7", - "payload_hex": "4212001df6c90f7eb625ab851315b6d972592b6f137f6df7", - "resolve": { - "now": 1767349056, - "contact_id": "contact-c", - "result": "ok" - } - } - ] -}