Skip to main content

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}