hidpp/channel/message.rs
1//! The two report widths a HID++ channel carries, and the raw message
2//! framing shared by both protocol versions.
3
4/// The ID of the HID report that is used to transmit short HID++ messages.
5pub const SHORT_REPORT_ID: u8 = 0x10;
6
7/// The length of short HID++ message reports (including report ID).
8pub const SHORT_REPORT_LENGTH: usize = 7;
9
10/// The ID of the HID report that is used to transmit long HID++ messages.
11pub const LONG_REPORT_ID: u8 = 0x11;
12
13/// The length of long HID++ message reports (including report ID).
14pub const LONG_REPORT_LENGTH: usize = 20;
15
16/// Represents an unversioned HID++ message.
17#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
18pub enum HidppMessage {
19 /// Represents a short HID++ message.
20 ///
21 /// Please check
22 /// [`HidppChannel::supports_short`](super::HidppChannel::supports_short)
23 /// before sending this kind of message.
24 Short([u8; SHORT_REPORT_LENGTH - 1]),
25
26 /// Represents a long HID++ message.
27 ///
28 /// Please check
29 /// [`HidppChannel::supports_long`](super::HidppChannel::supports_long)
30 /// before sending this kind of message.
31 Long([u8; LONG_REPORT_LENGTH - 1]),
32}
33
34impl HidppMessage {
35 /// Tries to read a HID++ message from raw data.
36 #[must_use]
37 pub fn read_raw(data: &[u8]) -> Option<Self> {
38 let (&report_id, rest) = data.split_first()?;
39
40 // The empty-remainder patterns enforce the exact report lengths.
41 if report_id == SHORT_REPORT_ID
42 && let Some((&payload, [])) = rest.split_first_chunk()
43 {
44 Some(HidppMessage::Short(payload))
45 } else if report_id == LONG_REPORT_ID
46 && let Some((&payload, [])) = rest.split_first_chunk()
47 {
48 Some(HidppMessage::Long(payload))
49 } else {
50 None
51 }
52 }
53
54 /// Writes a HID++ message in its raw byte form into a buffer.
55 ///
56 /// Returns the amount of written bytes.
57 pub fn write_raw(&self, buf: &mut [u8]) -> usize {
58 match self {
59 Self::Short(payload) => {
60 buf[0] = SHORT_REPORT_ID;
61 buf[1..SHORT_REPORT_LENGTH].copy_from_slice(payload);
62 SHORT_REPORT_LENGTH
63 }
64 Self::Long(payload) => {
65 buf[0] = LONG_REPORT_ID;
66 buf[1..LONG_REPORT_LENGTH].copy_from_slice(payload);
67 LONG_REPORT_LENGTH
68 }
69 }
70 }
71
72 /// The HID++ addressing header `(device_index, feature_index, function)` —
73 /// the first three payload bytes, present on both report kinds. Used only
74 /// for wire tracing (OpenLogi-specific; not in upstream hidpp).
75 pub(super) fn header(&self) -> (u8, u8, u8) {
76 let payload: &[u8] = match self {
77 Self::Short(payload) => payload,
78 Self::Long(payload) => payload,
79 };
80 (payload[0], payload[1], payload[2])
81 }
82
83 /// Re-frames a short message as a long one, leaving a long one untouched.
84 ///
85 /// The HID++ header bytes (device / feature / function|sw) sit at the same
86 /// offsets in both widths, so the only change is the report id plus
87 /// zero-padding the trailing payload. Used to send on a channel that
88 /// exposes only the long HID++ report — see
89 /// [`HidppChannel::normalize_outgoing`](super::HidppChannel). (OpenLogi
90 /// local addition.)
91 #[must_use]
92 pub(super) fn widened(self) -> Self {
93 match self {
94 Self::Short(payload) => {
95 let mut long = [0u8; LONG_REPORT_LENGTH - 1];
96 long[..payload.len()].copy_from_slice(&payload);
97 Self::Long(long)
98 }
99 long @ Self::Long(_) => long,
100 }
101 }
102}