summaryrefslogtreecommitdiff
path: root/src/hid.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid.rs')
-rw-r--r--src/hid.rs187
1 files changed, 187 insertions, 0 deletions
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<Report, Error> {
+ 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<T> {
+ pub command_id: u8,
+ pub data: T,
+}
+
+static_assertions::assert_eq_size!([u8; REPORT_PAYLOAD_LEN - MAX_REQUEST_DATA_LEN], Request<()>,);
+
+impl<T: serde::Serialize> Request<T> {
+ pub fn new(command_id: u8, data: T) -> Request<T> {
+ Request { command_id, data }
+ }
+}
+
+impl<'a, T: serde::Serialize> convert::TryFrom<&'a Request<T>> for Report {
+ type Error = Error;
+
+ fn try_from(request: &'a Request<T>) -> Result<Report, Error> {
+ 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<T> {
+ 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<T: serde::de::DeserializeOwned> Response<T> {
+ 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<T> {
+ type Error = Error;
+
+ fn try_from(report: &'a Report) -> Result<Response<T>, Error> {
+ let (response, _) = ssmarshal::deserialize(&report.payload)?;
+ Ok(response)
+ }
+}
+
+pub trait HidDeviceExt {
+ fn send<T: serde::Serialize>(&self, request: &Request<T>) -> Result<u32, Error>;
+ fn receive<T: serde::de::DeserializeOwned>(&self) -> Result<Response<T>, Error>;
+}
+
+impl HidDeviceExt for hidapi::HidDevice {
+ fn send<T: serde::Serialize>(&self, request: &Request<T>) -> Result<u32, Error> {
+ let report = Report::try_from(request)?;
+ let data = report.to_bytes()?;
+ self.send_feature_report(&data)?;
+ Ok(report.crc())
+ }
+
+ fn receive<T: serde::de::DeserializeOwned>(&self) -> Result<Response<T>, 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)
+ }
+}