openlogi_core/hid/pairing.rs
1//! Wire-format types for the Bolt/Unifying pairing flow — pure data, no I/O.
2//!
3//! The pairing session itself (discovery, notification decoding, the
4//! register writes) lives in `openlogi_hid::pairing`.
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9/// Selects which receiver a pairing operation targets.
10///
11/// Crosses the agent↔GUI IPC (`start_pairing`), so variant order is wire
12/// format — changes require a `PROTOCOL_VERSION` bump (guarded by
13/// `openlogi-ipc/tests/wire_format.rs`).
14#[derive(Clone, Debug, Serialize, Deserialize)]
15pub enum ReceiverSelector {
16 /// The first supported receiver found — fine for the common single-receiver case.
17 First,
18 /// A specific Bolt receiver by its unique ID.
19 BoltUid(String),
20}
21
22/// A single click in a pointer passkey sequence.
23#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
24pub enum Click {
25 /// Left mouse button click.
26 Left,
27 /// Right mouse button click.
28 Right,
29}
30
31/// How the user authenticates the device during Bolt pairing.
32///
33/// Crosses the agent↔GUI IPC (inside `PairingUpdate::Passkey`, [`Click`]
34/// included), so variant and field order are wire format — changes require a
35/// `PROTOCOL_VERSION` bump (guarded by
36/// `openlogi-ipc/tests/wire_format.rs`).
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub enum PasskeyMethod {
39 /// Type these digits on the new keyboard, then press Enter.
40 Keyboard(String),
41 /// On the new pointer, perform this left/right click sequence, then click
42 /// both buttons together.
43 Pointer {
44 /// Numeric passkey shown by the device.
45 passkey: String,
46 /// MSB-first click sequence derived from the passkey.
47 clicks: Vec<Click>,
48 },
49}
50
51/// Errors raised by pairing operations.
52///
53/// Pure data — no `hidpp`/`async-hid` types — but not itself a wire type:
54/// the agent maps it to `openlogi_ipc::PairingFailure`, which crosses
55/// the IPC boundary. The conversion from `async_hid::HidError` lives in
56/// `openlogi_hid::pairing`, which this crate must never depend on.
57#[derive(Clone, Debug, Error)]
58pub enum PairingError {
59 /// HID transport failure.
60 #[error("HID transport error: {0}")]
61 Hid(String),
62 /// No supported receiver matched the requested selector.
63 #[error("no supported pairing-capable receiver found")]
64 ReceiverNotFound,
65 /// HID++ receiver register read/write failed.
66 #[error("receiver register access failed: {0}")]
67 Register(String),
68 /// Pairing flow exceeded its timeout.
69 #[error("pairing timed out")]
70 Timeout,
71 /// Receiver reported a device-specific pairing error code.
72 #[error("receiver reported pairing error {0:#04x}")]
73 Device(u8),
74 /// Pairing flow was cancelled by the caller.
75 #[error("pairing was cancelled")]
76 Cancelled,
77 /// A receiver notification failed to decode; authentication cannot
78 /// proceed safely, so the flow fails instead of presenting bogus data.
79 #[error("malformed pairing notification ({0})")]
80 MalformedNotification(&'static str),
81}