Skip to main content

medius/types/
device_info.rs

1//! Decoded `RESP(DEVICE_INFO)`: the cloned device's USB identity, kind, and product (§4.3).
2
3use core::fmt;
4
5use crate::protocol::opcode::{DI_HAS_BOS, DI_HAS_SERIAL};
6
7/// Cloned device's primary kind, from its HID report descriptors and Boot declarations (§4.3).
8///
9/// A device with both kinds reads keyboard if it has a full key bitmap (unless it declares Boot for
10/// both) or declares Boot for its keyboard only. Otherwise it reads mouse.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12pub enum DeviceKind {
13    /// Neither a mouse nor a keyboard (e.g. a vendor-class pad with no HID interface), or nothing
14    /// cloned yet; [`DeviceInfo::is_cloned`] tells the two apart.
15    #[default]
16    Unknown,
17    Keyboard,
18    Mouse,
19}
20
21impl DeviceKind {
22    pub(crate) fn from_u8(v: u8) -> DeviceKind {
23        match v {
24            1 => DeviceKind::Keyboard,
25            2 => DeviceKind::Mouse,
26            _ => DeviceKind::Unknown,
27        }
28    }
29}
30
31impl fmt::Display for DeviceKind {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.write_str(match self {
34            DeviceKind::Unknown => "unknown",
35            DeviceKind::Keyboard => "keyboard",
36            DeviceKind::Mouse => "mouse",
37        })
38    }
39}
40
41/// Cloned device's USB identity, from its descriptors.
42#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
43pub struct DeviceInfo {
44    /// `idVendor`.
45    pub vid: u16,
46    /// `idProduct`.
47    pub pid: u16,
48    /// `bcdDevice` (device release).
49    pub bcd_device: u16,
50    /// `bcdUSB` (e.g. `0x0200`, `0x0201`).
51    pub bcd_usb: u16,
52    /// The clone serves a serial string.
53    pub has_serial: bool,
54    /// The clone serves a BOS descriptor (`bcdUSB >= 0x0201`).
55    pub has_bos: bool,
56    /// Primary kind (§4.3).
57    pub kind: DeviceKind,
58    /// `iProduct` string as ASCII, with `?` for each non-ASCII character (empty if the clone serves
59    /// none).
60    pub product: String,
61}
62
63impl DeviceInfo {
64    /// Whether a device is cloned: with none, the identity reads all-zero (§4.3).
65    pub fn is_cloned(&self) -> bool {
66        self.vid != 0 || self.pid != 0
67    }
68
69    /// Decodes a `RESP(DEVICE_INFO)` payload (§4.3).
70    pub(crate) fn from_payload(p: &[u8]) -> Option<Self> {
71        if p.len() < 11 {
72            return None;
73        }
74        let flags = p[9];
75        Some(DeviceInfo {
76            vid: u16::from_le_bytes([p[1], p[2]]),
77            pid: u16::from_le_bytes([p[3], p[4]]),
78            bcd_device: u16::from_le_bytes([p[5], p[6]]),
79            bcd_usb: u16::from_le_bytes([p[7], p[8]]),
80            has_serial: flags & DI_HAS_SERIAL != 0,
81            has_bos: flags & DI_HAS_BOS != 0,
82            kind: DeviceKind::from_u8(p[10]),
83            product: String::from_utf8_lossy(&p[11..]).into_owned(),
84        })
85    }
86}
87
88impl fmt::Display for DeviceInfo {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        if self.product.is_empty() {
91            write!(f, "{:04X}:{:04X}", self.vid, self.pid)
92        } else {
93            write!(f, "{:04X}:{:04X} {}", self.vid, self.pid, self.product)
94        }
95    }
96}