hidpp/receiver/bolt.rs
1//! Implements the Logi Bolt receiver.
2//!
3//! Bolt can be seen as a successor to the Unifying receiver. Both of them
4//! support up to 6 paired devices, but Bolt uses BTLE technology and introduces
5//! so-called passkeys for authenticating devices before pairing them.
6//!
7//! There is little to no public documentation about what registers Bolt
8//! supports (and they seem to differ quite substantially from registers
9//! supported by Unifying and other receivers), so this implementation is based
10//! largely on information gathered by looking at other codebases (primarily
11//! Solaar) and searching registers by fuzzing them.
12
13use std::sync::Arc;
14
15use crate::receiver::ListenerDropGuard;
16use derive_builder::Builder;
17use futures::{FutureExt, pin_mut, select};
18use num_enum::{IntoPrimitive, TryFromPrimitive};
19
20use super::{RECEIVER_DEVICE_INDEX, ReceiverError};
21use crate::{
22 channel::HidppChannel,
23 event::EventEmitter,
24 protocol::v10::{self, Hidpp10Error},
25};
26
27/// All USB vendor & product ID pairs that are known to identify Bolt receivers.
28pub const VPID_PAIRS: &[(u16, u16)] = &[(0x046d, 0xc548)];
29
30/// All known registers of the Bolt receiver.
31///
32/// In most cases you should not need to access these manually, as [`Receiver`]
33/// implements many features.
34#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36#[non_exhaustive]
37#[repr(u8)]
38pub enum Register {
39 /// Allows control over what notifications the receiver sends.
40 Notifications = 0x00,
41
42 /// Provides the amount of currently paired devices.
43 ///
44 /// This is exposed by [`Receiver::count_pairings`].
45 Connections = 0x02,
46
47 /// Provides information about the receiver and paired devices.
48 ///
49 /// It uses sub-registers, as defined in [`InfoSubRegister`], to
50 /// differentiate between different kinds of information.
51 ReceiverInfo = 0xb5,
52
53 /// Provides support for discovering devices that are ready to pair.
54 ///
55 /// Use [`Receiver::discover_devices`] and
56 /// [`Receiver::cancel_device_discovery`] to control device discovery.
57 DeviceDiscovery = 0xc0,
58
59 /// Provides pairing and unpairing support.
60 ///
61 /// Use [`Receiver::pair_device`] and [`Receiver::unpair_device`] for
62 /// pairing and unpairing.
63 Pairing = 0xc1,
64
65 /// Exposes the unique ID of the receiver. This seems to differ from the
66 /// serial number.
67 ///
68 /// Use [`Receiver::get_unique_id`] to query this value.
69 UniqueId = 0xfb,
70}
71
72/// All known sub-registers of the [`Register::ReceiverInfo`] register.
73#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize))]
75#[non_exhaustive]
76#[repr(u8)]
77pub enum InfoSubRegister {
78 /// Provides information about a specific paired device. The device index (4
79 /// bits) has to be added to the register address.
80 ///
81 /// Exposed by [`Receiver::get_device_pairing_information`].
82 DevicePairingInformation = 0x50, // 0x5N with N = device index
83
84 /// Provides the name of a paired device. The device index (4
85 /// bits) has to be added to the register address.
86 ///
87 /// Exposed by [`Receiver::get_device_codename`].
88 DeviceCodename = 0x60, // 0x6N with N = device index
89}
90
91/// Implements the Bolt receiver.
92#[derive(Clone)]
93pub struct Receiver {
94 chan: Arc<HidppChannel>,
95 emitter: Arc<EventEmitter<Event>>,
96 _listener: Arc<ListenerDropGuard>,
97}
98
99impl Receiver {
100 /// Tries to initialize a new [`Receiver`] from a raw HID++ channel.
101 ///
102 /// If no receiver could be found, or if the vendor and product IDs don't
103 /// match the ones of any known Bolt receiver, this function will return
104 /// [`ReceiverError::UnknownReceiver`].
105 pub fn new(chan: Arc<HidppChannel>) -> Result<Self, ReceiverError> {
106 if !VPID_PAIRS.contains(&(chan.vendor_id, chan.product_id)) {
107 return Err(ReceiverError::UnknownReceiver);
108 }
109
110 let emitter = Arc::new(EventEmitter::new());
111
112 let hdl = chan.add_msg_listener({
113 let emitter = Arc::clone(&emitter);
114
115 move |raw, matched| {
116 if matched {
117 return;
118 }
119
120 let parsed = v10::Message::from(raw);
121 let header = parsed.header();
122 let payload = parsed.extend_payload();
123
124 if header.device_index != RECEIVER_DEVICE_INDEX && header.sub_id != 0x41 {
125 return;
126 }
127
128 match header.sub_id {
129 // Device connection
130 0x41 => {
131 let Ok(kind) = DeviceKind::try_from(payload[1] & 0x0f) else {
132 return;
133 };
134
135 emitter.emit(Event::DeviceConnection(DeviceConnection {
136 index: header.device_index,
137 kind,
138 encrypted: payload[1] & (1 << 5) != 0,
139 online: payload[1] & (1 << 6) == 0,
140 wpid: u16::from_le_bytes(payload[2..=3].try_into().unwrap()),
141 }));
142 }
143 // Device discovery
144 0x4f => {
145 match payload[2] {
146 // Device data
147 0 => {
148 let Ok(kind) = DeviceKind::try_from(payload[4] & 0x0f) else {
149 return;
150 };
151
152 emitter.emit(Event::DeviceDiscoveryDeviceDetails {
153 counter: payload[0] as u16 + payload[1] as u16 * 256,
154 kind,
155 wpid: u16::from_le_bytes(payload[5..=6].try_into().unwrap()),
156 address: payload[7..=12].try_into().unwrap(),
157 authentication: payload[15],
158 });
159 }
160 // Device name
161 1 => {
162 let Some((counter, name)) = parse_discovery_name(&payload) else {
163 return;
164 };
165
166 emitter.emit(Event::DeviceDiscoveryDeviceName {
167 counter,
168 name: name.to_string(),
169 });
170 }
171 _ => (),
172 }
173 }
174 // Device discovery status
175 0x53 => {
176 emitter.emit(Event::DeviceDiscoveryStatus {
177 discovery_enabled: payload[0] == 0x00,
178 });
179 }
180 // Pairing status
181 0x54 => {
182 // payload[0] contains some kind of information about the status. I don't
183 // know how to map that though.
184
185 let error = if payload[1] == 0x00 {
186 None
187 } else {
188 let Ok(parsed) = PairingError::try_from(payload[1]) else {
189 return;
190 };
191
192 Some(parsed)
193 };
194
195 emitter.emit(Event::PairingStatus {
196 device_address: payload[2..=7].try_into().unwrap(),
197 pairing_error: error,
198 slot: if payload[8] == 0x00 {
199 None
200 } else {
201 Some(payload[8])
202 },
203 });
204 }
205 // Passkey request
206 0x4d => {
207 let Ok(passkey) = str::from_utf8(&payload[1..=6]) else {
208 return;
209 };
210
211 emitter.emit(Event::PairingPasskeyRequest {
212 device_address: payload[7..=12].try_into().unwrap(),
213 passkey: passkey.to_string(),
214 });
215 }
216 // Passkey pressed
217 0x4e => {
218 let Ok(press_type) = PairingPasskeyPressType::try_from(payload[0]) else {
219 return;
220 };
221
222 emitter.emit(Event::PairingPasskeyPressed {
223 device_address: payload[1..=6].try_into().unwrap(),
224 press_type,
225 });
226 }
227 _ => (),
228 }
229 }
230 });
231
232 Ok(Receiver {
233 _listener: Arc::new(ListenerDropGuard {
234 chan: Arc::clone(&chan),
235 hdl,
236 }),
237 chan,
238 emitter,
239 })
240 }
241
242 /// Creates a new listener for receiving receiver events.
243 pub fn listen(&self) -> async_channel::Receiver<Event> {
244 self.emitter.create_receiver()
245 }
246
247 /// Queries the current information about what notifications are enabled.
248 pub async fn get_notification_state(&self) -> Result<NotificationState, ReceiverError> {
249 let response = self
250 .chan
251 .read_register(
252 RECEIVER_DEVICE_INDEX,
253 Register::Notifications.into(),
254 [0u8; 3],
255 )
256 .await?;
257
258 Ok(NotificationState {
259 wireless_notifications: (response[1] & 1) != 0,
260 })
261 }
262
263 /// Configures what notifications are enabled and thus reported by the
264 /// receiver.
265 pub async fn set_notification_state(
266 &self,
267 state: NotificationState,
268 ) -> Result<(), ReceiverError> {
269 self.chan
270 .write_register(
271 RECEIVER_DEVICE_INDEX,
272 Register::Notifications.into(),
273 [0, if state.wireless_notifications { 1 } else { 0 }, 0],
274 )
275 .await?;
276
277 Ok(())
278 }
279
280 /// Counts the amount of devices currently paired to this receiver. The
281 /// devices don't have to be online to be included here as pairings are
282 /// persistent.
283 pub async fn count_pairings(&self) -> Result<u8, ReceiverError> {
284 let response = self
285 .chan
286 .read_register(
287 RECEIVER_DEVICE_INDEX,
288 Register::Connections.into(),
289 [0u8; 3],
290 )
291 .await?;
292
293 Ok(response[1])
294 }
295
296 /// Triggers device arrival notifications for all devices currently
297 /// connected to the receiver. This is useful for device enumeration.
298 ///
299 /// Check [`Self::get_notification_state`] first to make sure that
300 /// [`NotificationState::wireless_notifications`] is enabled.
301 pub async fn trigger_device_arrival(&self) -> Result<(), ReceiverError> {
302 self.chan
303 .write_register(
304 RECEIVER_DEVICE_INDEX,
305 Register::Connections.into(),
306 [0x02, 0x00, 0x00],
307 )
308 .await?;
309
310 Ok(())
311 }
312
313 /// Collects information about all paired devices by calling
314 /// [`Self::trigger_device_arrival`] and collecting incoming
315 /// [`Event::DeviceConnection`] events.
316 ///
317 /// Check [`Self::get_notification_state`] first to make sure that
318 /// [`NotificationState::wireless_notifications`] is enabled.
319 pub async fn collect_paired_devices(&self) -> Result<Vec<DeviceConnection>, ReceiverError> {
320 // The idea here is that, when triggering fake device arrival notifications, the
321 // receiver will send the register write confirmation message only AFTER sending
322 // all arrival notifications.
323 // So we will trigger device arrival notifications and continue collecting those
324 // until the original future has completed.
325
326 let mut devices = vec![];
327
328 let rx = self.listen();
329 let fin = self.trigger_device_arrival().fuse();
330 pin_mut!(fin);
331
332 loop {
333 select! {
334 _ = fin => break,
335 res = rx.recv().fuse() => {
336 let Ok(Event::DeviceConnection(connection)) = res else {
337 continue;
338 };
339
340 devices.push(connection);
341 }
342 }
343 }
344
345 Ok(devices)
346 }
347
348 /// Retrieves the unique ID of the receiver. This is not the same as the
349 /// serial number.
350 pub async fn get_unique_id(&self) -> Result<String, ReceiverError> {
351 let response = self
352 .chan
353 .read_long_register(RECEIVER_DEVICE_INDEX, Register::UniqueId.into(), [0u8; 3])
354 .await?;
355
356 // When decoding the last 8 bytes of the response to their ASCII representation
357 // we seem to get a valid hex string representing 4 bytes of data.
358 // Interpreting this hex string as little endian we seem to get the same decimal
359 // value the Options+ software calls `udid` (unique device identifier?). I am
360 // not sure what this is about and it may be a (major) coincidence that these
361 // values match for my receiver, but it could be worth keeping this in mind.
362
363 // I have no clue how to retrieve the serial number of the receiver.
364
365 Ok(str::from_utf8(&response)
366 .map_err(|_| Hidpp10Error::UnsupportedResponse)?
367 .to_string())
368 }
369
370 /// Provides the pairing information of a specific paired device by its
371 /// index.
372 pub async fn get_device_pairing_information(
373 &self,
374 device_index: u8,
375 ) -> Result<DevicePairingInformation, ReceiverError> {
376 let response = self
377 .chan
378 .read_long_register(
379 RECEIVER_DEVICE_INDEX,
380 Register::ReceiverInfo.into(),
381 [
382 u8::from(InfoSubRegister::DevicePairingInformation) + (device_index & 0x0f),
383 0x00,
384 0x00,
385 ],
386 )
387 .await?;
388
389 Ok(DevicePairingInformation {
390 wpid: u16::from_le_bytes(response[2..=3].try_into().unwrap()),
391 kind: DeviceKind::try_from(response[1] & 0x0f)
392 .map_err(|_| Hidpp10Error::UnsupportedResponse)?,
393 encrypted: response[1] & (1 << 5) != 0,
394 online: response[1] & (1 << 6) == 0,
395 unit_id: response[4..=7].try_into().unwrap(),
396 })
397 }
398
399 /// Provides the codename of a specific paired device by its index.
400 pub async fn get_device_codename(&self, device_index: u8) -> Result<String, ReceiverError> {
401 // For device names longer than 13 characters this may need to be called
402 // multiple times with different parameters. I don't have a device with
403 // such a name to be able to test this.
404
405 let response = self
406 .chan
407 .read_long_register(
408 RECEIVER_DEVICE_INDEX,
409 Register::ReceiverInfo.into(),
410 [
411 u8::from(InfoSubRegister::DeviceCodename) + (device_index & 0x0f),
412 0x01,
413 0x00,
414 ],
415 )
416 .await?;
417
418 Ok(parse_codename(&response)
419 .ok_or(Hidpp10Error::UnsupportedResponse)?
420 .to_string())
421 }
422
423 /// Unpairs a device from the receiver by its index.
424 pub async fn unpair_device(&self, device_index: u8) -> Result<(), ReceiverError> {
425 let mut payload = [0u8; 16];
426 payload[0] = 0x03;
427 payload[1] = device_index;
428
429 self.chan
430 .write_long_register(RECEIVER_DEVICE_INDEX, Register::Pairing.into(), payload)
431 .await?;
432
433 Ok(())
434 }
435
436 /// Starts the pairing process for a new device.
437 ///
438 /// The required `address` and `authentication` values are usually
439 /// discovered from the [`Event::DeviceDiscoveryDeviceDetails`] event which
440 /// is emitted regularly when actively discovering available devices
441 /// ([`Self::discover_devices`]).
442 ///
443 /// `entropy` specifies how complex the authentication passkey should be.
444 /// For mice, this defines the amount of keypresses (left or right) the user
445 /// has to perform. Not all values seem to be supported.
446 pub async fn pair_device(
447 &self,
448 slot: u8,
449 address: [u8; 6],
450 authentication: u8,
451 entropy: u8,
452 ) -> Result<(), ReceiverError> {
453 let mut payload = [0u8; 16];
454 payload[0] = 0x01;
455 payload[1] = slot;
456 payload[2..=7].copy_from_slice(&address);
457 payload[8] = authentication;
458 payload[9] = entropy;
459
460 self.chan
461 .write_long_register(RECEIVER_DEVICE_INDEX, Register::Pairing.into(), payload)
462 .await?;
463
464 Ok(())
465 }
466
467 /// Starts device discovery for `timeout` seconds ([`None`] = default, seems
468 /// to be 30s). The maximum supported value is 60s.
469 ///
470 /// While device discovery is enabled,
471 /// [`Event::DeviceDiscoveryDeviceDetails`] and
472 /// [`Event::DeviceDiscoveryDeviceName`] events are emitted for every
473 /// discovered device.
474 pub async fn discover_devices(&self, timeout: Option<u8>) -> Result<(), ReceiverError> {
475 self.chan
476 .write_register(
477 RECEIVER_DEVICE_INDEX,
478 Register::DeviceDiscovery.into(),
479 [timeout.unwrap_or(0x00), 0x01, 0x00],
480 )
481 .await?;
482
483 Ok(())
484 }
485
486 /// Cancels the device discovery process.
487 pub async fn cancel_device_discovery(&self) -> Result<(), ReceiverError> {
488 self.chan
489 .write_register(
490 RECEIVER_DEVICE_INDEX,
491 Register::DeviceDiscovery.into(),
492 [0x00, 0x02, 0x00],
493 )
494 .await?;
495
496 Ok(())
497 }
498}
499
500/// Parse a device-discovery name notification (sub-id `0x4f`, kind `1`).
501///
502/// `payload[3]` is the device-reported name length. The byte comes straight
503/// off the radio, so it must never index past the report: a length that does
504/// not fit the packet (or non-UTF-8 bytes) drops the event instead of
505/// panicking the listener.
506fn parse_discovery_name(payload: &[u8; 17]) -> Option<(u16, &str)> {
507 let len = usize::from(payload[3]);
508 let end = 4usize.checked_add(len)?;
509 let name = str::from_utf8(payload.get(4..end)?).ok()?;
510 Some((payload[0] as u16 + payload[1] as u16 * 256, name))
511}
512
513/// Extract the codename chunk from a `DeviceCodename` register read.
514///
515/// `response[2]` is the device-reported name length. A name longer than the
516/// 13 bytes one response carries is clamped to the chunk present (fetching
517/// the rest takes further reads with different parameters); a length byte
518/// pointing past the response must not panic. `None` for non-UTF-8 bytes.
519fn parse_codename(response: &[u8; 16]) -> Option<&str> {
520 let end = 3usize.saturating_add(usize::from(response[2]));
521 let raw = response.get(3..end.min(response.len()))?;
522 str::from_utf8(raw).ok()
523}
524
525/// Indicates which notifications are enabled and thus sent by the receiver.
526///
527/// This information can be queried using [`Receiver::get_notification_state`].
528#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Builder)]
529#[cfg_attr(feature = "serde", derive(serde::Serialize))]
530#[non_exhaustive]
531pub struct NotificationState {
532 /// Whether the receiver sends device arrival/removal notifications.
533 pub wireless_notifications: bool,
534}
535
536/// Represents information about a paired device.
537///
538/// This information can be queried using
539/// [`Receiver::get_device_pairing_information`].
540#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
541#[cfg_attr(feature = "serde", derive(serde::Serialize))]
542#[non_exhaustive]
543pub struct DevicePairingInformation {
544 pub wpid: u16,
545 pub kind: DeviceKind,
546 pub encrypted: bool,
547 pub online: bool,
548 pub unit_id: [u8; 4],
549}
550
551/// Represents the kind of a device paired to a Bolt receiver.
552#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
553#[cfg_attr(feature = "serde", derive(serde::Serialize))]
554#[non_exhaustive]
555#[repr(u8)]
556pub enum DeviceKind {
557 Unknown = 0x00,
558 Keyboard = 0x01,
559 Mouse = 0x02,
560 Numpad = 0x03,
561 Presenter = 0x04,
562 Remote = 0x07,
563 Trackball = 0x08,
564 Touchpad = 0x09,
565 Tablet = 0x0a,
566 Gamepad = 0x0b,
567 Joystick = 0x0c,
568 Headset = 0x0d,
569}
570
571/// Represents an event emitted by the receiver.
572///
573/// You can listen to these events using [`Receiver::listen`]. Only enabled
574/// notifications as indicated by [`Receiver::get_notification_state`] are
575/// emitted.
576#[derive(Clone, PartialEq, Eq, Hash, Debug)]
577#[cfg_attr(feature = "serde", derive(serde::Serialize))]
578#[non_exhaustive]
579pub enum Event {
580 /// Is emitted whenever a device connects to or disconnects from the
581 /// receiver, but only if [`NotificationState::wireless_notifications`] is
582 /// enabled.
583 ///
584 /// Can be triggered for all paired devices using
585 /// [`Receiver::trigger_device_arrival`] to allow easy device enumeration.
586 ///
587 /// [`Receiver::collect_paired_devices`] implements a simple mechanism to
588 /// collect all paired devices.
589 DeviceConnection(DeviceConnection),
590
591 /// Is emitted whenever the device discovery status changes.
592 DeviceDiscoveryStatus { discovery_enabled: bool },
593
594 /// Is emitted many times for every device discovered using
595 /// [`Receiver::discover_devices`].
596 ///
597 /// This event contains device details, including its address required to
598 /// start pairing. The [`Event::DeviceDiscoveryDeviceName`] event will also
599 /// be emitted and contains the device name.
600 DeviceDiscoveryDeviceDetails {
601 /// The incrementing event counter. This can be used to map
602 /// [`Event::DeviceDiscoveryDeviceDetails`] and
603 /// [`Event::DeviceDiscoveryDeviceName`] events.
604 counter: u16,
605
606 kind: DeviceKind,
607 wpid: u16,
608
609 /// The address of the device required to pair it using
610 /// [`Receiver::pair_device`].
611 ///
612 /// This can also be used as the unique device identifier when
613 /// collecting discovered devices.
614 address: [u8; 6],
615
616 /// The authentication type(s) the device supports. Unfortunately, there
617 /// is not much information about this value and whether it is a
618 /// single value or a bitfield.
619 authentication: u8,
620 },
621
622 /// Is emitted many times for every device discovered using
623 /// [`Receiver::discover_devices`].
624 ///
625 /// This event only contains the device name. Device details will be
626 /// provided using the [`Event::DeviceDiscoveryDeviceDetails`] event.
627 DeviceDiscoveryDeviceName {
628 /// The incrementing event counter. This can be used to map
629 /// [`Event::DeviceDiscoveryDeviceDetails`] and
630 /// [`Event::DeviceDiscoveryDeviceName`] events.
631 counter: u16,
632
633 name: String,
634 },
635
636 /// Is emitted whenever the status of a pairing process changes.
637 PairingStatus {
638 device_address: [u8; 6],
639 pairing_error: Option<PairingError>,
640
641 /// The receiver slot the newly paired device was paired to. This can be
642 /// used as the device index for subsequent operations.
643 slot: Option<u8>,
644 },
645
646 /// Is emitted once the receiver requests a passkey to be entered on a
647 /// device that should be paired to it.
648 PairingPasskeyRequest {
649 device_address: [u8; 6],
650
651 /// The passkey the user has to enter in order to pair the device.
652 ///
653 /// Depending on the device and authentication type, this value has
654 /// different implications.
655 ///
656 /// For mice, this value will be a valid 6-digit number. After parsing
657 /// this into an integer, the (least significant) bits represent
658 /// the sequence of mouse presses (`0` = left, `1` = right) the
659 /// user has to perform, with an additional press of both mouse
660 /// buttons simultaneously.
661 ///
662 /// The amount of bits significant to this equals to the `entropy`
663 /// passed to [`Receiver::pair_device`].
664 passkey: String,
665 },
666
667 /// Is emitted for every keypress a user performs while entering a pairing
668 /// passkey.
669 PairingPasskeyPressed {
670 device_address: [u8; 6],
671
672 /// The type of the keypress the user performed.
673 ///
674 /// Every passkey sequence starts with an event where this value is set
675 /// to [`PairingPasskeyPressType::Initialization`]. Each time the user
676 /// presses a key, an event with a press type of
677 /// [`PairingPasskeyPressType::Keypress`] is emitted. Once the user
678 /// submits their passkey, this value will be
679 /// [`PairingPasskeyPressType::Submit`].
680 press_type: PairingPasskeyPressType,
681 },
682}
683
684/// Represents a device connected to a Bolt receiver.
685///
686/// This information is emitted by the [`Event::DeviceConnection`] event and can
687/// be conveniently collected using [`Receiver::collect_paired_devices`].
688#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
689#[cfg_attr(feature = "serde", derive(serde::Serialize))]
690#[non_exhaustive]
691pub struct DeviceConnection {
692 pub index: u8,
693 pub kind: DeviceKind,
694 pub encrypted: bool,
695 pub online: bool,
696 pub wpid: u16,
697}
698
699/// Represents an error during device pairing.
700///
701/// This is reported by the [`Event::PairingStatus`] event.
702#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TryFromPrimitive, IntoPrimitive)]
703#[cfg_attr(feature = "serde", derive(serde::Serialize))]
704#[non_exhaustive]
705#[repr(u8)]
706pub enum PairingError {
707 DeviceTimeout = 0x01,
708 Failed = 0x02,
709}
710
711/// Represents the type of a single passkey press.
712///
713/// This is reported by the [`Event::PairingPasskeyPressed`] event, which also
714/// includes some further information about the context of these values.
715#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TryFromPrimitive, IntoPrimitive)]
716#[cfg_attr(feature = "serde", derive(serde::Serialize))]
717#[non_exhaustive]
718#[repr(u8)]
719pub enum PairingPasskeyPressType {
720 Initialization = 0x00,
721 Keypress = 0x01,
722 Submit = 0x04,
723}
724
725#[cfg(test)]
726mod tests {
727 use super::*;
728
729 #[test]
730 fn discovery_name_with_oversized_length_is_dropped() {
731 let mut payload = [0u8; 17];
732 payload[3] = 200;
733
734 assert_eq!(parse_discovery_name(&payload), None);
735 }
736
737 #[test]
738 fn discovery_name_within_bounds_parses() {
739 let mut payload = [0u8; 17];
740 payload[0] = 7;
741 payload[3] = 4;
742 payload[4..8].copy_from_slice(b"Casa");
743
744 assert_eq!(parse_discovery_name(&payload), Some((7, "Casa")));
745 }
746
747 #[test]
748 fn discovery_name_rejects_invalid_utf8() {
749 let mut payload = [0u8; 17];
750 payload[3] = 2;
751 payload[4] = 0xff;
752 payload[5] = 0xfe;
753
754 assert_eq!(parse_discovery_name(&payload), None);
755 }
756
757 #[test]
758 fn codename_with_oversized_length_clamps_to_available_chunk() {
759 let mut response = [0u8; 16];
760 response[2] = 200;
761 response[3..16].copy_from_slice(b"MX Anywhere 3");
762
763 assert_eq!(parse_codename(&response), Some("MX Anywhere 3"));
764 }
765
766 #[test]
767 fn codename_within_bounds_parses() {
768 let mut response = [0u8; 16];
769 response[2] = 5;
770 response[3..8].copy_from_slice(b"Casa!");
771
772 assert_eq!(parse_codename(&response), Some("Casa!"));
773 }
774
775 #[test]
776 fn codename_rejects_invalid_utf8() {
777 let mut response = [0u8; 16];
778 response[2] = 2;
779 response[3] = 0xff;
780 response[4] = 0xfe;
781
782 assert_eq!(parse_codename(&response), None);
783 }
784}