use super::{Completion, Data, Error, Sense, Status, Transport};
use nusb::{
DeviceInfo, Endpoint, Interface, MaybeFuture,
transfer::{Buffer, Bulk, BulkOrInterrupt, EndpointDirection, In, Out, TransferError},
};
use std::{
io,
thread::sleep,
time::{Duration, Instant},
};
use tracing::*;
const PHASE_CHECK_CODE: u8 = 0xD0;
const PHASE_NONE: u8 = 0x00;
const PHASE_STATUS: u8 = 0x01;
const PHASE_DATA_OUT: u8 = 0x02;
const PHASE_DATA_IN: u8 = 0x03;
const PHASE_BUSY: u8 = 0x04;
const BUSY_WAIT: Duration = Duration::from_millis(5);
const BUSY_WAIT_MAX: Duration = Duration::from_millis(250);
const PHASE_WAIT: Duration = Duration::from_secs(7);
const RECHECK_WAIT: Duration = Duration::from_secs(1);
const RESYNC_TIMEOUT: Duration = Duration::from_millis(200);
const RESYNC_LIMIT: usize = 1 << 20;
#[derive(Clone, Copy)]
struct Budget {
deadline: Instant,
total: Duration,
}
impl Budget {
fn new(total: Duration) -> Self {
Self {
deadline: Instant::now() + total,
total,
}
}
fn left(&self) -> Result<Duration, Error> {
let left = self.deadline.saturating_duration_since(Instant::now());
if left.is_zero() {
Err(Error::Timeout(self.total))
} else {
Ok(left)
}
}
}
pub struct UsbTransport {
ep_out: Endpoint<Bulk, Out>,
ep_in: Endpoint<Bulk, In>,
in_max_packet: usize,
#[allow(dead_code)]
interface: Interface,
dirty: bool,
}
fn transfer_err(e: TransferError, timeout: Duration) -> Error {
let kind = match &e {
TransferError::Cancelled => return Error::Timeout(timeout),
TransferError::Stall => io::ErrorKind::BrokenPipe,
TransferError::Disconnected => io::ErrorKind::NotConnected,
TransferError::InvalidArgument => io::ErrorKind::InvalidInput,
TransferError::Fault | TransferError::Unknown(_) => io::ErrorKind::Other,
};
Error::Io(io::Error::new(kind, e))
}
fn clear_stall<T: BulkOrInterrupt, D: EndpointDirection>(
ep: &mut Endpoint<T, D>,
e: TransferError,
) {
if e != TransferError::Stall {
return;
}
match ep.clear_halt().wait() {
Ok(()) => warn!("the unit halted the endpoint, cleared it for the next command"),
Err(e) => warn!(%e, "the unit halted the endpoint and it would not clear"),
}
}
enum Chunk {
Got(usize),
TooMuch(usize),
}
impl Chunk {
fn count(self, asked: usize) -> Result<usize, Error> {
match self {
Chunk::Got(n) => Ok(n),
Chunk::TooMuch(n) => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"device sent {n} bytes for a {asked}-byte read, so the stream is out of step"
),
)
.into()),
}
}
}
enum Phase {
Code(u8),
Silent,
Stale(usize),
}
enum Attempt {
Done(Completion),
Resend { drain: bool },
Desync(Error),
}
impl UsbTransport {
pub fn open(info: DeviceInfo) -> io::Result<Self> {
let device = info.open().wait()?;
if device.active_configuration().is_err() {
device
.set_configuration(1)
.wait()
.map_err(io::Error::other)?;
}
let interface = device.claim_interface(0).wait()?;
let ep_out = interface.endpoint::<Bulk, Out>(0x01)?;
let ep_in = interface.endpoint::<Bulk, In>(0x82)?;
let in_max_packet = ep_in.max_packet_size();
if in_max_packet == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"bulk IN endpoint reports zero max packet size",
));
}
debug!(?device, "Opened scanner");
Ok(Self {
ep_out,
ep_in,
in_max_packet,
interface,
dirty: false,
})
}
fn resync(&mut self) {
let mut dropped = 0usize;
while dropped < RESYNC_LIMIT {
match self.transfer_in(self.in_max_packet, RESYNC_TIMEOUT) {
Ok(b) if !b.is_empty() => dropped += b.len(),
_ => {
if dropped > 0 {
warn!(
bytes = dropped,
"the last command left its answer in the pipe, dropped it to get back in step"
);
}
self.dirty = false;
return;
}
}
}
warn!(
bytes = dropped,
"the unit has more to say than we will drop, so we are still out of step"
);
}
fn write_out(&mut self, bytes: &[u8], timeout: Duration) -> Result<(), Error> {
let sent = self
.ep_out
.transfer_blocking(bytes.into(), timeout)
.into_result();
match sent {
Ok(_) => Ok(()),
Err(e) => {
clear_stall(&mut self.ep_out, e);
Err(transfer_err(e, timeout))
}
}
}
fn transfer_in(&mut self, want: usize, timeout: Duration) -> Result<Buffer, Error> {
let req = want.max(1).div_ceil(self.in_max_packet) * self.in_max_packet;
let got = self
.ep_in
.transfer_blocking(Buffer::new(req), timeout)
.into_result();
got.map_err(|e| {
clear_stall(&mut self.ep_in, e);
transfer_err(e, timeout)
})
}
fn read_in(&mut self, out: &mut [u8], timeout: Duration) -> Result<Chunk, Error> {
let buf = self.transfer_in(out.len(), timeout)?;
let n = buf.len().min(out.len());
out[..n].copy_from_slice(&buf[..n]);
match buf.len() > out.len() {
true => Ok(Chunk::TooMuch(buf.len())),
false => Ok(Chunk::Got(n)),
}
}
fn read_data_in(&mut self, out: &mut [u8], budget: Budget) -> Result<usize, Error> {
let mut done = 0;
let mut piece = out.len();
while done < out.len() {
let want = piece.min(out.len() - done);
let last = done + want == out.len();
let got = match self.read_in(&mut out[done..done + want], budget.left()?)? {
Chunk::TooMuch(n) if last => {
trace!(padding = n - want, "dropped the tail of the last packet");
want
}
chunk => chunk.count(want)?,
};
if got == 0 {
break;
}
if done == 0 && got < out.len() {
piece = got;
}
done += got;
}
Ok(done)
}
fn phase_check(&mut self, budget: Budget, bound: Duration) -> Result<Phase, Error> {
match self.write_out(&[PHASE_CHECK_CODE], budget.left()?.min(bound)) {
Ok(()) => {}
Err(Error::Timeout(_)) => return Ok(Phase::Silent),
Err(e) => return Err(e),
}
let mut phase = [0u8; 1];
match self.read_in(&mut phase, budget.left()?.min(bound)) {
Ok(Chunk::Got(1)) => {
trace!(phase = format!("{:02X}h", phase[0]), "phase");
Ok(Phase::Code(phase[0]))
}
Ok(Chunk::Got(n) | Chunk::TooMuch(n)) => Ok(Phase::Stale(n)),
Err(Error::Timeout(_)) => Ok(Phase::Silent),
Err(e) => Err(e),
}
}
fn exchange(&mut self, cdb: &[u8], data: Data, budget: Budget) -> Result<Attempt, Error> {
match self.write_out(cdb, budget.left()?.min(PHASE_WAIT)) {
Ok(()) => {}
Err(Error::Timeout(_)) => return Ok(Attempt::Resend { drain: true }),
Err(e) => return Err(e),
}
let mut wait = BUSY_WAIT;
let mut bound = PHASE_WAIT;
let phase = loop {
match self.phase_check(budget, bound)? {
Phase::Code(PHASE_BUSY) => {
sleep(wait.min(budget.left()?));
wait = (wait * 2).min(BUSY_WAIT_MAX);
bound = RECHECK_WAIT;
}
Phase::Code(phase) => break phase,
Phase::Silent => return Ok(Attempt::Resend { drain: true }),
Phase::Stale(n) => {
return Ok(Attempt::Desync(
io::Error::new(
io::ErrorKind::InvalidData,
format!("a phase check read {n} bytes off the pipe"),
)
.into(),
));
}
}
};
let transferred = match (phase, data) {
(PHASE_STATUS, Data::None) => 0,
(PHASE_STATUS, d) => {
debug!(data = ?d, "the unit went to status with no data phase");
0
}
(PHASE_DATA_OUT, Data::Out(x)) => {
self.write_out(x, budget.left()?)?;
x.len()
}
(PHASE_DATA_IN, Data::In(x)) => self.read_data_in(x, budget)?,
(PHASE_NONE, _) => {
return Ok(Attempt::Desync(
io::Error::new(io::ErrorKind::InvalidData, "no phase after the command").into(),
));
}
(p @ (PHASE_DATA_IN | PHASE_DATA_OUT), d) => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("the unit asked for phase {p:02X}h but the command carries {d:?}"),
)
.into());
}
(x, _) => {
return Ok(Attempt::Desync(
io::Error::new(
io::ErrorKind::InvalidData,
format!("{x:02X}h is not a phase code"),
)
.into(),
));
}
};
let mut sb = [0u8; 8];
let n = self.read_in(&mut sb, budget.left()?)?.count(sb.len())?;
if n != 8 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("status phase returned {n} bytes, expected 8"),
)
.into());
}
let status = Status::from(sb[0]);
let sense = Some(Sense {
key: sb[1] & 0x0F,
asc: sb[2],
ascq: sb[3],
tsc: Some(sb[4]),
ili: false,
information: None,
raw: sb.to_vec(),
});
Ok(Attempt::Done(Completion {
status,
sense,
transferred,
}))
}
}
impl Transport for UsbTransport {
fn max_transfer(&self) -> usize {
128 * 1024
}
fn execute(
&mut self,
cdb: &[u8],
mut data: Data,
timeout: Duration,
) -> Result<Completion, Error> {
if self.dirty {
self.resync();
}
if enabled!(Level::TRACE) {
let hex: Vec<String> = cdb.iter().map(|b| format!("{b:02X}")).collect();
trace!(cdb = hex.join(" "), ?data, "command");
}
let budget = Budget::new(timeout);
let mut drained = false;
loop {
budget.left()?;
self.dirty = true;
match self.exchange(cdb, data.reborrow(), budget)? {
Attempt::Done(completion) => {
self.dirty = false;
return Ok(completion);
}
Attempt::Resend { drain } => {
if drain {
debug!("the unit stopped answering, we send the command again");
self.resync();
}
sleep(BUSY_WAIT.min(budget.left()?));
}
Attempt::Desync(e) => {
if drained {
return Err(e);
}
drained = true;
warn!(%e, "the pipe is out of step, we drop it and send the command again");
self.resync();
}
}
}
}
}