1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// Copyright (C) 2021-2022 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: Apache-2.0 or MIT

use std::{borrow::Cow, convert::TryFrom, fmt, time::Duration};

use crate::error::{RequestError, ResponseError};

/// A HID device.
///
/// The HID device must provide be able to send and receive packets with a fixed size.
pub trait Device {
    /// The type for information about this device.
    type Info: DeviceInfo;

    /// Sends a packet with the given data to the device.
    fn send(&self, data: &[u8]) -> Result<(), RequestError>;

    /// Receives a packet from the device, writes its data to the given buffer and returns a slice
    /// of the buffer containing the response data.
    ///
    /// If the received data includes a report number, it is not part of the returned slice.  The
    /// buffer must be longer than the [`DeviceInfo::packet_size`][].  If the device does not send
    /// a packet within the given timeout, [`ResponseError::Timeout`][] must be returned.
    fn receive<'a>(
        &self,
        buffer: &'a mut [u8],
        timeout: Option<Duration>,
    ) -> Result<&'a [u8], ResponseError>;
}

#[cfg(feature = "hidapi")]
impl Device for hidapi::HidDevice {
    type Info = hidapi::DeviceInfo;

    fn send(&self, data: &[u8]) -> Result<(), RequestError> {
        let n = self
            .write(data)
            .map_err(|err| RequestError::PacketSendingFailed(err.into()))?;
        if n == data.len() {
            Ok(())
        } else {
            Err(RequestError::IncompleteWrite)
        }
    }

    fn receive<'a>(
        &self,
        buffer: &'a mut [u8],
        timeout: Option<Duration>,
    ) -> Result<&'a [u8], ResponseError> {
        let duration = if let Some(timeout) = timeout {
            i32::try_from(timeout.as_millis())
                .map_err(|err| ResponseError::PacketReceivingFailed(err.into()))?
        } else {
            -1
        };
        let n = self
            .read_timeout(buffer, duration)
            .map_err(|err| ResponseError::PacketReceivingFailed(err.into()))?;
        if n == buffer.len() {
            Ok(&buffer[1..n])
        } else if n == 0 {
            Err(ResponseError::Timeout)
        } else {
            Ok(&buffer[..n])
        }
    }
}

/// Information about a HID device.
pub trait DeviceInfo: fmt::Debug {
    /// Returns the packet size for this HID device.
    ///
    /// This size does not include the report number (if used).
    fn packet_size(&self) -> usize {
        64
    }

    /// Returns the vendor ID for this device.
    fn vendor_id(&self) -> u16;

    /// Returns the product ID for this device.
    fn product_id(&self) -> u16;

    /// Returns the HID path of this device.
    ///
    /// HID implementations will probably store the path as a `CStr` or `OsStr`.  It should be
    /// converted to a UTF-8 string as well as possible.
    fn path(&self) -> Cow<'_, str>;
}

#[cfg(feature = "hidapi")]
impl DeviceInfo for hidapi::DeviceInfo {
    fn vendor_id(&self) -> u16 {
        hidapi::DeviceInfo::vendor_id(self)
    }

    fn product_id(&self) -> u16 {
        hidapi::DeviceInfo::product_id(self)
    }

    fn path(&self) -> Cow<'_, str> {
        String::from_utf8_lossy(hidapi::DeviceInfo::path(self).to_bytes())
    }
}