Skip to main content

hidpp/protocol/
v20.rs

1//! Implements functionality specific to HID++2.0.
2
3use num_enum::{IntoPrimitive, TryFromPrimitive};
4use thiserror::Error;
5
6use crate::{
7    channel::{ChannelError, HidppChannel, HidppMessage, LONG_REPORT_LENGTH, SHORT_REPORT_LENGTH},
8    nibble::{self, U4},
9};
10
11/// Represents the header that every [`HidppMessage`] of HID++2.0 starts with.
12#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14pub struct MessageHeader {
15    /// The index of the device involved in the communication.
16    pub device_index: u8,
17
18    /// The index of the feature the message belongs to.
19    ///
20    /// This is not the same as the feature ID, but the index returned from a
21    /// feature enumeration request.
22    pub feature_index: u8,
23
24    /// The ID of the function involved in the communication.
25    pub function_id: U4,
26
27    /// The ID of the software communicating with the device.
28    pub software_id: U4,
29}
30
31/// Represents a HID++2.0 message.
32#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34pub enum Message {
35    /// Represents a short HID++2.0 message with 3 bytes of payload.
36    Short(MessageHeader, [u8; SHORT_REPORT_LENGTH - 4]),
37
38    /// Represents a long HID++2.0 message with 16 bytes of payload.
39    Long(MessageHeader, [u8; LONG_REPORT_LENGTH - 4]),
40}
41
42impl Message {
43    /// Extracts the header of the message.
44    pub fn header(&self) -> MessageHeader {
45        match *self {
46            Message::Short(header, _) => header,
47            Message::Long(header, _) => header,
48        }
49    }
50
51    /// Extracts the payload of the message and fits it into an array capable of
52    /// containing the longest possible payload, filling the rest up with
53    /// zeroes.
54    pub fn extend_payload(&self) -> [u8; LONG_REPORT_LENGTH - 4] {
55        match *self {
56            Message::Short(_, payload) => {
57                let mut data = [0; LONG_REPORT_LENGTH - 4];
58                data[..SHORT_REPORT_LENGTH - 4].copy_from_slice(&payload);
59                data
60            }
61            Message::Long(_, payload) => payload,
62        }
63    }
64}
65
66impl From<HidppMessage> for Message {
67    fn from(msg: HidppMessage) -> Self {
68        match msg {
69            HidppMessage::Short(payload) => Message::Short(
70                MessageHeader {
71                    device_index: payload[0],
72                    feature_index: payload[1],
73                    function_id: U4::from_hi(payload[2]),
74                    software_id: U4::from_lo(payload[2]),
75                },
76                payload[3..].try_into().unwrap(),
77            ),
78            HidppMessage::Long(payload) => Message::Long(
79                MessageHeader {
80                    device_index: payload[0],
81                    feature_index: payload[1],
82                    function_id: U4::from_hi(payload[2]),
83                    software_id: U4::from_lo(payload[2]),
84                },
85                payload[3..].try_into().unwrap(),
86            ),
87        }
88    }
89}
90
91impl From<Message> for HidppMessage {
92    fn from(msg: Message) -> Self {
93        match msg {
94            Message::Short(header, payload) => {
95                let mut data = [0u8; SHORT_REPORT_LENGTH - 1];
96                data[0] = header.device_index;
97                data[1] = header.feature_index;
98                data[2] = nibble::combine(header.function_id, header.software_id);
99                data[3..].copy_from_slice(&payload);
100
101                HidppMessage::Short(data)
102            }
103            Message::Long(header, payload) => {
104                let mut data = [0u8; LONG_REPORT_LENGTH - 1];
105                data[0] = header.device_index;
106                data[1] = header.feature_index;
107                data[2] = nibble::combine(header.function_id, header.software_id);
108                data[3..].copy_from_slice(&payload);
109
110                HidppMessage::Long(data)
111            }
112        }
113    }
114}
115
116impl HidppChannel {
117    /// Sends a HID++2.0 message across the channel and waits for a response
118    /// that matches the message header.
119    ///
120    /// This method simply calls [`Self::send`] with a pre-built response
121    /// predicate comparing the headers of the outgoing and incoming message.
122    pub async fn send_v20(&self, msg: Message) -> Result<Message, Hidpp20Error> {
123        let header = msg.header();
124
125        let response = Message::from(
126            self.send(msg.into(), move |&response| {
127                let resp_msg = Message::from(response);
128                let resp_header = resp_msg.header();
129
130                // A HID++2.0 error response sets the feature index to 0xFF and moves all header
131                // values starting from the real feature index one byte to the right.
132                let is_error = resp_header.device_index == header.device_index
133                    && resp_header.feature_index == 0xff
134                    && nibble::combine(resp_header.function_id, resp_header.software_id)
135                        == header.feature_index
136                    && resp_msg.extend_payload()[0]
137                        == nibble::combine(header.function_id, header.software_id);
138
139                is_error || resp_header == header
140            })
141            .await?,
142        );
143
144        if response.header().feature_index == 0xff {
145            let err = ErrorType::try_from(response.extend_payload()[1])
146                .map_err(|_| Hidpp20Error::UnsupportedResponse)?;
147
148            return Err(Hidpp20Error::Feature(err));
149        }
150
151        Ok(response)
152    }
153}
154
155/// Represents the type of an error a HID++2.0 device returns if a feature
156/// function fails.
157#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize))]
159#[non_exhaustive]
160#[repr(u8)]
161pub enum ErrorType {
162    NoError = 0,
163    Unknown = 1,
164    InvalidArgument = 2,
165    OutOfRange = 3,
166    HwError = 4,
167    LogitechInternal = 5,
168    InvalidFeatureIndex = 6,
169    InvalidFunctionId = 7,
170    Busy = 8,
171    Unsupported = 9,
172}
173
174/// Represents an error that may occur when calling a HID++2.0 feature function.
175#[derive(Debug, Error)]
176#[non_exhaustive]
177pub enum Hidpp20Error {
178    /// Indicates that an error occurred while communicating across the HID++
179    /// channel.
180    #[error("the HID++ channel returned an error")]
181    Channel(#[from] ChannelError),
182
183    /// Indicates that a call to a HID++2.0 feature function resulted in an
184    /// error.
185    #[error("a HID++2.0 feature returned an error")]
186    Feature(ErrorType),
187
188    /// Indicates that a received response is not fully supported.
189    #[error("the received response from the device is (partly) unsupported")]
190    UnsupportedResponse,
191}