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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::time::Duration;
use crate::error::{Error, Result};
use crate::protocol::FrameType;
use crate::protocol::command::raw_payload;
use crate::types::{Direction, Setup, TransferOutcome, TransferStatus};
use super::Device;
/// How long a [`transfer`](Device::transfer) waits for the device's reply before
/// [`Error::QueryTimeout`](crate::Error::QueryTimeout). Longer than
/// [`DEFAULT_QUERY_TIMEOUT`](crate::DEFAULT_QUERY_TIMEOUT), as a real device can be slower than a
/// box-local query.
pub const DEFAULT_TRANSFER_TIMEOUT: Duration = Duration::from_millis(1500);
impl Device {
// The box drops an advanced-layer (§3.14) frame with no reply while the opt-in is off, so the
// config-rate setters check first. `raw` does not: it runs per report, and a query costs a round
// trip.
pub(crate) fn require_imperfect(&self) -> Result<()> {
if self.query_imperfect()?.allowed {
Ok(())
} else {
Err(Error::ImperfectRequired)
}
}
/// `RAW` (§3.14): put `bytes` verbatim on cloned endpoint `ep` in `direction`; fire-and-forget.
///
/// `ep` is the bare endpoint number (0 to 15). [`Direction::IN`] emits to the game PC;
/// [`Direction::OUT`] relays to the real device. [`Direction::Both`] gives [`Error::RawDirection`]
/// and the bearing-relative pair [`Error::RelativeDirection`], before any frame goes out. The
/// write is stateless and one-shot: the next native report on that endpoint carries native state,
/// and `RAW` bypasses the [rewrite rules](Device::set_rewrite). A raw IN report never rides a
/// native one, so on an endpoint the device reports on every poll it takes its own poll, within
/// two device reports.
///
/// `bytes` is at most 510 long, else [`Error::FrameTooLong`]. The box drops an interrupt payload
/// past the endpoint's `wMaxPacketSize`, in either direction. On a HID IN endpoint, a payload of
/// exactly `wMaxPacketSize` ends with a zero-length packet when the endpoint's largest Input report
/// is longer, as a native report of that length does. A bulk payload splits at
/// `wMaxPacketSize` on the wire and ends with a short packet, or a zero-length one on an exact
/// multiple. An OUT packet waits its turn behind the device's two-packet hold, as the game PC's
/// writes do, and theirs wait behind it; up to 16 wait across all endpoints, and one past that is
/// dropped and counted in [`relay_drops`](crate::Stats::relay_drops).
///
/// Admitted by [`allow_imperfect_clones`](Device::allow_imperfect_clones): with the opt-in off the
/// box drops the frame with no reply, and this still returns `Ok`.
/// [`query_imperfect`](Device::query_imperfect) reports the state; read it once at setup, not per
/// call (a round trip each).
///
/// ```no_run
/// # use medius::{Device, Direction, Result};
/// # fn main() -> Result<()> {
/// let device = Device::find()?;
/// device.allow_imperfect_clones(true)?;
/// device.raw(1, Direction::IN, &[0x00, 0x01, 0x00, 0x00])?; // one report on interrupt-IN endpoint 1
/// # Ok(()) }
/// ```
pub fn raw(&self, ep: u8, direction: Direction, bytes: &[u8]) -> Result<()> {
validate_raw_direction(direction)?;
self.link
.send(FrameType::Raw, &raw_payload(ep, direction, bytes))
}
/// `TRANSFER` (§3.14): run one control transfer against the real device and return its reply.
///
/// `ep` is 0 for EP0 or a control endpoint the device declares. `setup` is the eight-byte setup
/// packet; `out` the OUT data stage (empty for an IN transfer). The [`TransferOutcome`] carries
/// the [`status`](TransferOutcome::status) and any IN data: a status other than
/// [`Ok`](TransferStatus::Ok) is a protocol result, not a link error, so it is returned, not
/// raised; the surrounding `Ok` means the box replied.
///
/// Admitted by [`allow_imperfect_clones`](Device::allow_imperfect_clones): with the opt-in off the
/// box replies [`Refused`](TransferStatus::Refused) without reaching the device. Runs over its own
/// inter-chip link pair, apart from the game PC's EP0 proxy, one at a time. Uses
/// [`DEFAULT_TRANSFER_TIMEOUT`]; [`transfer_timeout`](Device::transfer_timeout) sets another.
///
/// ```no_run
/// # use medius::{Device, Result, Setup, TransferStatus};
/// # fn main() -> Result<()> {
/// let device = Device::find()?;
/// device.allow_imperfect_clones(true)?;
/// // GET_DESCRIPTOR(Device): the 18-byte device descriptor.
/// let reply = device.transfer(0, Setup::new(0x80, 0x06, 0x0100, 0x0000, 18), &[])?;
/// if reply.status == TransferStatus::Ok {
/// println!("device descriptor: {:02x?}", reply.data());
/// }
/// # Ok(()) }
/// ```
pub fn transfer(&self, ep: u8, setup: Setup, out: &[u8]) -> Result<TransferOutcome> {
self.transfer_timeout(ep, setup, out, DEFAULT_TRANSFER_TIMEOUT)
}
/// [`transfer`](Device::transfer) with an explicit reply timeout.
///
/// The box gives up on a control transfer after its ~800 ms window. A shorter `timeout` abandons
/// the wait before a slow device replies, and if 256 further transfers to the same `ep` then wrap
/// the sequence number inside that window, a late reply can correlate to a later transfer. Keep it
/// at or above the box window, as [`DEFAULT_TRANSFER_TIMEOUT`] is.
pub fn transfer_timeout(
&self,
ep: u8,
setup: Setup,
out: &[u8],
timeout: Duration,
) -> Result<TransferOutcome> {
let (status, data) = self.link.transfer(ep, setup, out, timeout)?;
Ok(TransferOutcome {
status: TransferStatus::from_u8(status),
data,
})
}
}
// Only IN and OUT name one endpoint flow. The bearing-relative pair needs an emit time a raw write
// lacks, and `Both` names two flows; the box would resolve either to OUT.
pub(crate) fn validate_raw_direction(direction: Direction) -> Result<()> {
match direction {
Direction::Positive | Direction::Negative => Ok(()),
d if d.is_relative() => Err(Error::RelativeDirection {
direction: d,
what: "raw endpoint",
}),
d => Err(Error::RawDirection { direction: d }),
}
}