use super::{Completion, Data, Error, Sense, Status, Transport};
use nusb::{
DeviceInfo, Endpoint, Interface, MaybeFuture,
transfer::{Buffer, Bulk, In, Out, TransferError},
};
use std::{
io,
thread::sleep,
time::{Duration, Instant},
};
use tracing::*;
const PHASE_CHECK_CODE: u8 = 0xD0;
const STATUS_RECEPTION_CODE: u8 = 0x06;
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 RESYNC_TIMEOUT: Duration = Duration::from_millis(200);
const RESYNC_LIMIT: usize = 1 << 20;
#[allow(dead_code)]
pub struct UsbTransport {
ep_out: Endpoint<Bulk, Out>,
ep_in: Endpoint<Bulk, In>,
in_max_packet: usize,
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))
}
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
.ep_in
.transfer_blocking(Buffer::new(self.in_max_packet), RESYNC_TIMEOUT)
.into_result()
{
Ok(b) if !b.is_empty() => dropped += b.len(),
_ => break,
}
}
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;
}
fn write_out(&mut self, bytes: &[u8], timeout: Duration) -> Result<(), Error> {
self.ep_out
.transfer_blocking(bytes.into(), timeout)
.into_result()
.map_err(|e| transfer_err(e, timeout))?;
Ok(())
}
fn read_in(&mut self, out: &mut [u8], timeout: Duration) -> Result<usize, Error> {
let req = out.len().max(1).div_ceil(self.in_max_packet) * self.in_max_packet;
let buf = self
.ep_in
.transfer_blocking(Buffer::new(req), timeout)
.into_result()
.map_err(|e| transfer_err(e, timeout))?;
if buf.len() > out.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"device sent {} bytes for a {}-byte read, so the stream is out of step",
buf.len(),
out.len()
),
)
.into());
}
out[..buf.len()].copy_from_slice(&buf);
Ok(buf.len())
}
}
impl Transport for UsbTransport {
fn max_transfer(&self) -> usize {
128 * 1024
}
fn execute(&mut self, cdb: &[u8], data: Data, timeout: Duration) -> Result<Completion, Error> {
if self.dirty {
self.resync();
}
self.dirty = true;
let done = self.exchange(cdb, data, timeout);
self.dirty = done.is_err();
done
}
}
impl UsbTransport {
fn exchange(&mut self, cdb: &[u8], data: Data, timeout: Duration) -> Result<Completion, Error> {
self.write_out(cdb, timeout)?;
let mut phase = [0u8; 1];
let deadline = Instant::now() + timeout;
loop {
let left = deadline.saturating_duration_since(Instant::now());
if left.is_zero() {
return Err(Error::Timeout(timeout));
}
self.write_out(&[PHASE_CHECK_CODE], left)?;
if self.read_in(&mut phase, left)? == 0 {
return Err(
io::Error::new(io::ErrorKind::InvalidData, "empty phase response").into(),
);
}
if phase[0] != PHASE_BUSY {
break;
}
sleep(Duration::from_millis(5));
}
let transferred = match (phase[0], data) {
(PHASE_STATUS, Data::None) => 0,
(PHASE_STATUS, x) => {
debug!("We requested a non-none data phase {:?} but got none", x);
0
}
(PHASE_DATA_OUT, Data::Out(x)) => {
self.write_out(x, timeout)?;
x.len()
}
(PHASE_DATA_IN, Data::In(x)) => self.read_in(x, timeout)?,
(PHASE_NONE, _) => {
return Err(
io::Error::new(io::ErrorKind::InvalidData, "no phase after command").into(),
);
}
(p @ (PHASE_DATA_IN | PHASE_DATA_OUT), d) => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("device reported phase {p:#04x} but command supplied {d:?}"),
)
.into());
}
(x, _) => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid phase byte {x:#04x}"),
)
.into());
}
};
self.write_out(&[STATUS_RECEPTION_CODE], timeout)?;
let mut sb = [0u8; 8];
let n = self.read_in(&mut sb, timeout)?;
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],
asc: sb[2],
ascq: sb[3],
tsc: Some(sb[4]),
ili: false,
information: None,
raw: sb.to_vec(),
})
};
Ok(Completion {
status,
sense,
transferred,
})
}
}