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) } }