use num_enum::{IntoPrimitive, TryFromPrimitive};
use thiserror::Error;
use crate::{
channel::{
ChannelError, HidppChannel, HidppMessage, LONG_REPORT_LENGTH, SEND_RESPONSE_TIMEOUT,
SHORT_REPORT_LENGTH,
},
nibble::{self, U4},
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MessageHeader {
pub device_index: u8,
pub feature_index: u8,
pub function_id: U4,
pub software_id: U4,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Message {
Short(MessageHeader, [u8; SHORT_REPORT_LENGTH - 4]),
Long(MessageHeader, [u8; LONG_REPORT_LENGTH - 4]),
}
impl Message {
#[must_use]
pub fn header(&self) -> MessageHeader {
match *self {
Message::Short(header, _) | Message::Long(header, _) => header,
}
}
#[must_use]
pub fn extend_payload(&self) -> [u8; LONG_REPORT_LENGTH - 4] {
match *self {
Message::Short(_, payload) => {
let mut data = [0; LONG_REPORT_LENGTH - 4];
data[..SHORT_REPORT_LENGTH - 4].copy_from_slice(&payload);
data
}
Message::Long(_, payload) => payload,
}
}
}
impl From<HidppMessage> for Message {
fn from(msg: HidppMessage) -> Self {
match msg {
HidppMessage::Short(payload) => {
let [_, _, _, rest @ ..] = payload;
Message::Short(
MessageHeader {
device_index: payload[0],
feature_index: payload[1],
function_id: U4::from_hi(payload[2]),
software_id: U4::from_lo(payload[2]),
},
rest,
)
}
HidppMessage::Long(payload) => {
let [_, _, _, rest @ ..] = payload;
Message::Long(
MessageHeader {
device_index: payload[0],
feature_index: payload[1],
function_id: U4::from_hi(payload[2]),
software_id: U4::from_lo(payload[2]),
},
rest,
)
}
}
}
}
impl From<Message> for HidppMessage {
fn from(msg: Message) -> Self {
match msg {
Message::Short(header, payload) => {
let mut data = [0u8; SHORT_REPORT_LENGTH - 1];
data[0] = header.device_index;
data[1] = header.feature_index;
data[2] = nibble::combine(header.function_id, header.software_id);
data[3..].copy_from_slice(&payload);
HidppMessage::Short(data)
}
Message::Long(header, payload) => {
let mut data = [0u8; LONG_REPORT_LENGTH - 1];
data[0] = header.device_index;
data[1] = header.feature_index;
data[2] = nibble::combine(header.function_id, header.software_id);
data[3..].copy_from_slice(&payload);
HidppMessage::Long(data)
}
}
}
}
impl HidppChannel {
pub async fn send_v20(&self, msg: Message) -> Result<Message, Hidpp20Error> {
let header = msg.header();
let response = self
.send(msg.into(), v20_response_predicate(header))
.await?;
decode_v20_response(response)
}
pub async fn send_v20_write_through(
&self,
msg: Message,
matches_payload: impl Fn(&Message) -> bool + Send + 'static,
) -> Result<Message, Hidpp20Error> {
let matches_header = v20_response_predicate(msg.header());
let response = self
.send_write_through(
msg.into(),
move |raw| {
let response = Message::from(*raw);
matches_header(raw)
&& (response.header().feature_index == 0xff || matches_payload(&response))
},
SEND_RESPONSE_TIMEOUT,
)
.await?;
decode_v20_response(response)
}
}
fn v20_response_predicate(
header: MessageHeader,
) -> impl Fn(&HidppMessage) -> bool + Send + 'static {
move |&response| {
let response = Message::from(response);
let response_header = response.header();
let is_error = response_header.device_index == header.device_index
&& response_header.feature_index == 0xff
&& nibble::combine(response_header.function_id, response_header.software_id)
== header.feature_index
&& response.extend_payload()[0]
== nibble::combine(header.function_id, header.software_id);
is_error || response_header == header
}
}
fn decode_v20_response(response: HidppMessage) -> Result<Message, Hidpp20Error> {
let response = Message::from(response);
if response.header().feature_index == 0xff {
let err = ErrorType::try_from(response.extend_payload()[1])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?;
return Err(Hidpp20Error::Feature(err));
}
Ok(response)
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ErrorType {
NoError = 0,
Unknown = 1,
InvalidArgument = 2,
OutOfRange = 3,
HwError = 4,
LogitechInternal = 5,
InvalidFeatureIndex = 6,
InvalidFunctionId = 7,
Busy = 8,
Unsupported = 9,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Hidpp20Error {
#[error("the HID++ channel returned an error")]
Channel(#[from] ChannelError),
#[error("a HID++2.0 feature returned an error")]
Feature(ErrorType),
#[error("the received response from the device is (partly) unsupported")]
UnsupportedResponse,
}