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
128
129
130
131
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 native 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-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 gives [`Error::RelativeDirection`],
/// before any frame is sent. The write is stateless and one-shot: the next native report on
/// that endpoint carries native state. `RAW` bypasses the [rewrite rules](Device::set_rewrite).
/// A raw IN report never rides a native one: on an endpoint the real device reports on every
/// poll, it takes a separate poll, within two native reports.
///
/// `bytes` is <=510 long, else [`Error::FrameTooLong`]. The box drops an interrupt payload
/// longer than the endpoint's `wMaxPacketSize`, in either direction. On a HID IN endpoint whose
/// largest Input report is longer than `wMaxPacketSize`, a payload of exactly `wMaxPacketSize`
/// ends with a zero-length packet, 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. Raw OUT packets and the game PC's writes queue together behind the real
/// device's two-packet hold. Up to 16 wait across all endpoints; one past that is dropped and
/// counted in [`relay_drops`](crate::Stats::relay_drops). The box queues 8 packets per vendor
/// IN endpoint. When that queue is full, an interrupt endpoint loses its oldest packet and a
/// bulk endpoint drops the raw packet sent into it; either counts in `relay_drops`.
///
/// Needs [`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 real device declares. `setup` is the 8-byte
/// setup packet; `out` is 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 outer `Ok` means the box replied.
///
/// Needs [`allow_imperfect_clones`](Device::allow_imperfect_clones): with the opt-in off the
/// box replies [`Refused`](TransferStatus::Refused) without reaching the real device. While the
/// game PC has the clone suspended, the real device is suspended too and the box replies
/// [`Nak`](TransferStatus::Nak). Transfers cross the inter-chip link one at a time, as a
/// request and reply separate from the game PC's EP0 proxy. 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 ~800 ms. A shorter `timeout` can abandon the
/// wait before a slow device replies. If 256 more transfers to the same `ep` then wrap the
/// sequence number within those ~800 ms, the late reply can be matched to a later transfer.
/// Keep `timeout` >=800 ms, 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, which 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 }),
}
}