From 434e61c231e142b6f5f8d81eb25f4ef97686d85a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 27 Mar 2021 16:10:53 +0100 Subject: Initial commit --- src/hid.rs | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 src/hid.rs (limited to 'src/hid.rs') diff --git a/src/hid.rs b/src/hid.rs new file mode 100644 index 0000000..291216c --- /dev/null +++ b/src/hid.rs @@ -0,0 +1,187 @@ +use std::convert; +use std::convert::TryFrom as _; + +use crate::crc32; +use crate::Error; + +pub const REPORT_LEN: usize = 65; +pub const REPORT_PAYLOAD_LEN: usize = REPORT_LEN - 5; +pub const MAX_REQUEST_DATA_LEN: usize = REPORT_PAYLOAD_LEN - 1; +pub const MAX_RESPONSE_DATA_LEN: usize = REPORT_PAYLOAD_LEN - 7; + +serde_big_array::big_array! { BigArray; REPORT_PAYLOAD_LEN } + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +struct Report { + report_id: u8, + #[serde(with = "BigArray")] + payload: [u8; REPORT_PAYLOAD_LEN], + crc: [u8; 4], +} + +static_assertions::assert_eq_size!([u8; REPORT_LEN], Report); + +impl Report { + fn new(report_id: u8, payload: [u8; REPORT_PAYLOAD_LEN]) -> Report { + let crc = crc32::crc(&payload); + Report { + report_id, + payload, + crc: crc.to_le_bytes(), + } + } + + fn from_bytes(data: [u8; REPORT_LEN]) -> Result { + let (report, num_bytes): (Report, _) = ssmarshal::deserialize(&data)?; + if num_bytes != REPORT_LEN { + return Err(format!("Only {} of {} report bytes read", num_bytes, REPORT_LEN).into()); + } + Ok(report) + } + + fn to_bytes(&self) -> Result<[u8; REPORT_LEN], Error> { + let mut buf = [0; REPORT_LEN]; + let num_bytes = ssmarshal::serialize(&mut buf, self)?; + if num_bytes != REPORT_LEN { + return Err(format!("Invalid report length: {}", num_bytes).into()); + } + Ok(buf) + } + + fn crc(&self) -> u32 { + u32::from_le_bytes(self.crc) + } + + pub fn check_crc(&self) -> Result<(), Error> { + let crc = crc32::crc(&self.payload); + if self.crc() != crc { + Err(format!( + "Warning: Report has wrong CRC: got {:x}, expected {:x}", + self.crc(), + crc + ) + .into()) + } else { + Ok(()) + } + } +} + +impl Default for Report { + fn default() -> Report { + Report::new(0, [0; REPORT_PAYLOAD_LEN]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct Request { + pub command_id: u8, + pub data: T, +} + +static_assertions::assert_eq_size!([u8; REPORT_PAYLOAD_LEN - MAX_REQUEST_DATA_LEN], Request<()>,); + +impl Request { + pub fn new(command_id: u8, data: T) -> Request { + Request { command_id, data } + } +} + +impl<'a, T: serde::Serialize> convert::TryFrom<&'a Request> for Report { + type Error = Error; + + fn try_from(request: &'a Request) -> Result { + let mut payload = [0; REPORT_PAYLOAD_LEN]; + let num_bytes = ssmarshal::serialize(&mut payload, &request)?; + if num_bytes > REPORT_PAYLOAD_LEN { + return Err(format!("Invalid request length: {}", num_bytes).into()); + } + Ok(Report::new(0, payload)) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)] +#[repr(u8)] +pub enum DeviceStatus { + Ok, + Busy, + Error, + ReceivedReport, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)] +#[repr(u8)] +pub enum CommandStatus { + Ok, + WrongCrc, + WrongSlot, + SlotNotProgrammed, + WrongPassword, + NotAuthorized, + TimestampWarning, + NoNameError, + NotSupported, + UnknownCommand, + AesDecFailed, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)] +pub struct Response { + pub device_status: DeviceStatus, + pub command_id: u8, + pub last_crc: [u8; 4], + pub command_status: CommandStatus, + pub data: T, +} + +static_assertions::assert_eq_size!( + [u8; REPORT_PAYLOAD_LEN - MAX_RESPONSE_DATA_LEN], + Response<()>, +); + +impl Response { + pub fn last_crc(&self) -> u32 { + u32::from_le_bytes(self.last_crc) + } +} + +impl<'a, T: serde::de::DeserializeOwned> convert::TryFrom<&'a Report> for Response { + type Error = Error; + + fn try_from(report: &'a Report) -> Result, Error> { + let (response, _) = ssmarshal::deserialize(&report.payload)?; + Ok(response) + } +} + +pub trait HidDeviceExt { + fn send(&self, request: &Request) -> Result; + fn receive(&self) -> Result, Error>; +} + +impl HidDeviceExt for hidapi::HidDevice { + fn send(&self, request: &Request) -> Result { + let report = Report::try_from(request)?; + let data = report.to_bytes()?; + self.send_feature_report(&data)?; + Ok(report.crc()) + } + + fn receive(&self) -> Result, Error> { + let mut buf = [0; REPORT_LEN]; + let n = self.get_feature_report(&mut buf)?; + if n != REPORT_LEN { + return Err(format!("Invalid report length: {}", n).into()); + } + let report = Report::from_bytes(buf)?; + if report.report_id != 0 { + return Err(format!("Invalid report ID: {}", report.report_id).into()); + } + let response = Response::try_from(&report)?; + // TODO: is the CRC set for all other status variants? + if response.device_status != DeviceStatus::Busy { + report.check_crc()?; + } + Ok(response) + } +} -- cgit v1.2.1