aboutsummaryrefslogtreecommitdiff
path: root/src/hid.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/hid.rs148
1 files changed, 108 insertions, 40 deletions
diff --git a/src/hid.rs b/src/hid.rs
index 72f67fa..f8f9447 100644
--- a/src/hid.rs
+++ b/src/hid.rs
@@ -6,6 +6,7 @@
//! `HIDIOCSFEATURE`, `HIDIOCGINPUT`, `HIDIOCGSOUTPUT` ioctls plus plain
//! `read`/`write` on the interrupt endpoints.
+use std::cmp::Ordering;
use std::fs;
use std::io;
use std::os::fd::AsRawFd;
@@ -27,10 +28,12 @@ const IOC_READ: u32 = 2;
const IOC_WRITE: u32 = 1;
fn ioc(dir: u32, ty: u8, nr: u32, size: u32) -> libc::c_ulong {
- ((dir << IOC_DIRSHIFT)
- | ((ty as u32) << IOC_TYPESHIFT)
- | (nr << IOC_NRSHIFT)
- | (size << IOC_SIZESHIFT)) as libc::c_ulong
+ u64::from(
+ (dir << IOC_DIRSHIFT)
+ | (u32::from(ty) << IOC_TYPESHIFT)
+ | (nr << IOC_NRSHIFT)
+ | (size << IOC_SIZESHIFT),
+ )
}
const HIDIOCGFEATURE_NR: u32 = 0x06;
@@ -62,11 +65,19 @@ pub struct HidDevice {
/// Enumerate hidraw devices matching `vid`/`pid` by reading the sysfs `uevent`
/// of each `/sys/class/hidraw/hidrawN`.
+///
+/// # Errors
+///
+/// Returns an error if `/sys/class/hidraw` cannot be listed.
pub fn find_devices(vid: u16, pid: u16) -> io::Result<Vec<HidDevice>> {
find_with(|v, p| v == vid && p == pid)
}
/// Find the first (only) MMX 150 dongle HID device.
+///
+/// # Errors
+///
+/// Propagates errors from [`find_devices`].
pub fn find_mmx150() -> io::Result<Option<HidDevice>> {
Ok(find_devices(BEYERDYNAMIC_VID, MMX150_DONGLE_PID)?
.into_iter()
@@ -74,6 +85,10 @@ pub fn find_mmx150() -> io::Result<Option<HidDevice>> {
}
/// Enumerate all hidraw devices belonging to a vendor.
+///
+/// # Errors
+///
+/// Returns an error if `/sys/class/hidraw` cannot be listed.
pub fn find_devices_by_vendor(vid: u16) -> io::Result<Vec<HidDevice>> {
find_with(|v, _| v == vid)
}
@@ -122,12 +137,17 @@ pub struct Hidraw {
}
impl Hidraw {
- pub fn open(devnode: impl AsRef<Path>) -> io::Result<Hidraw> {
+ /// Open a hidraw device node for reading and writing.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the node cannot be opened for read/write access.
+ pub fn open(devnode: impl AsRef<Path>) -> io::Result<Self> {
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.open(devnode.as_ref())?;
- Ok(Hidraw {
+ Ok(Self {
file,
devnode: devnode.as_ref().to_path_buf(),
})
@@ -135,33 +155,63 @@ impl Hidraw {
/// `HIDIOCGFEATURE`: fetch `report_id` into `data` (report length + 1
/// bytes including the report-id byte). Returns bytes received.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the ioctl fails.
pub fn get_feature(&self, report_id: u8, len: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; len];
- buf[0] = report_id;
- self.raw_ioctl(hid_get_feature_cmd(len as u32), &mut buf)?;
+ if let Some(first) = buf.first_mut() {
+ *first = report_id;
+ }
+ self.raw_ioctl(hid_get_feature_cmd(report_size(len)), &mut buf)?;
Ok(buf)
}
/// `HIDIOCSFEATURE`: set a feature report (`data[0]` is the report id).
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the ioctl fails.
pub fn set_feature(&self, data: &[u8]) -> io::Result<usize> {
- self.raw_ioctl(hid_set_feature_cmd(data.len() as u32), &mut data.to_vec())
+ self.raw_ioctl(
+ hid_set_feature_cmd(report_size(data.len())),
+ &mut data.to_vec(),
+ )
}
/// `HIDIOCGINPUT`: fetch an input report by id.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the ioctl fails.
pub fn get_input(&self, report_id: u8, len: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; len];
- buf[0] = report_id;
- self.raw_ioctl(hid_get_input_cmd(len as u32), &mut buf)?;
+ if let Some(first) = buf.first_mut() {
+ *first = report_id;
+ }
+ self.raw_ioctl(hid_get_input_cmd(report_size(len)), &mut buf)?;
Ok(buf)
}
/// `HIDIOCGSOUTPUT`: send an output report (`data[0]` is the report id)
/// via a control transfer, without going through the interrupt endpoint.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the ioctl fails.
pub fn send_output(&self, data: &[u8]) -> io::Result<usize> {
- self.raw_ioctl(hid_get_output_cmd(data.len() as u32), &mut data.to_vec())
+ self.raw_ioctl(
+ hid_get_output_cmd(report_size(data.len())),
+ &mut data.to_vec(),
+ )
}
/// Read from the interrupt IN endpoint (asynchronous input reports).
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the read fails.
pub fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
use std::io::Read;
self.file.read(buf)
@@ -169,50 +219,63 @@ impl Hidraw {
/// Wait up to `timeout` for one input report on the interrupt IN
/// endpoint. Returns `Ok(None)` if none arrived in time.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if `select` or `read` fails with anything other than
+ /// `EINTR`.
pub fn read_with_timeout(&mut self, timeout: Duration) -> io::Result<Option<Vec<u8>>> {
- let deadline = Instant::now() + timeout;
+ let deadline = Instant::now()
+ .checked_add(timeout)
+ .unwrap_or_else(Instant::now);
loop {
- let now = Instant::now();
- if now >= deadline {
+ let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
return Ok(None);
- }
+ };
+ let fd = self.file.as_raw_fd();
let mut set = unsafe { std::mem::zeroed::<libc::fd_set>() };
- unsafe { libc::FD_ZERO(&mut set) };
- unsafe { libc::FD_SET(self.file.as_raw_fd(), &mut set) };
- let remaining = deadline.saturating_duration_since(now);
+ unsafe { libc::FD_ZERO(std::ptr::addr_of_mut!(set)) };
+ unsafe { libc::FD_SET(fd, std::ptr::addr_of_mut!(set)) };
let mut tv = libc::timeval {
- tv_sec: remaining.as_secs() as libc::time_t,
- tv_usec: remaining.subsec_micros() as libc::suseconds_t,
+ tv_sec: libc::time_t::try_from(remaining.as_secs()).unwrap_or(libc::time_t::MAX),
+ tv_usec: libc::suseconds_t::from(remaining.subsec_micros()),
};
let n = unsafe {
libc::select(
- self.file.as_raw_fd() + 1,
- &mut set,
+ fd.saturating_add(1),
+ std::ptr::addr_of_mut!(set),
std::ptr::null_mut(),
std::ptr::null_mut(),
- &mut tv,
+ std::ptr::addr_of_mut!(tv),
)
};
- if n < 0 {
- let err = io::Error::last_os_error();
- if err.kind() == io::ErrorKind::Interrupted {
- continue;
+ match n.cmp(&0) {
+ Ordering::Less => {
+ let err = io::Error::last_os_error();
+ if err.kind() != io::ErrorKind::Interrupted {
+ return Err(err);
+ }
+ }
+ Ordering::Equal => return Ok(None),
+ Ordering::Greater => {
+ let mut buf = [0u8; 256];
+ match self.read(&mut buf) {
+ Ok(len) => {
+ return Ok(Some(buf.get(..len).unwrap_or(&[]).to_vec()));
+ }
+ Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
+ Err(e) => return Err(e),
+ }
}
- return Err(err);
- }
- if n == 0 {
- return Ok(None);
- }
- let mut buf = [0u8; 256];
- match self.read(&mut buf) {
- Ok(len) => return Ok(Some(buf[..len].to_vec())),
- Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
- Err(e) => return Err(e),
}
}
}
/// Write to the interrupt OUT endpoint.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the write fails.
pub fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
use std::io::Write;
self.file.write(buf)
@@ -224,13 +287,13 @@ impl Hidraw {
libc::ioctl(
self.file.as_raw_fd(),
request,
- buf.as_mut_ptr() as *mut libc::c_void,
+ buf.as_mut_ptr().cast::<libc::c_void>(),
)
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
- Ok(ret as usize)
+ Ok(usize::try_from(ret).unwrap_or(0))
}
}
@@ -240,6 +303,11 @@ impl std::os::fd::AsRawFd for Hidraw {
}
}
+/// Clamp a report length to what the ioctl size field can hold.
+fn report_size(len: usize) -> u32 {
+ u32::try_from(len).unwrap_or(u32::MAX)
+}
+
fn read_sysfs(path: &Path) -> Option<String> {
fs::read_to_string(path).ok()
}