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