Skip to main content

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}