valentine

Terminal control panel for the Focusrite Scarlett 18i20 — a from-scratch replacement for Focusrite Control.
Log | Files | Refs | README | LICENSE

transport.rs (8952B)


      1 //! The command channel to the device.
      2 //!
      3 //! [`Transport`] is the seam between the protocol logic and the wire: the real
      4 //! [`UsbTransport`] drives endpoint 0 with rusb (libusb), and tests use a mock.
      5 //!
      6 //! rusb is used rather than nusb because on macOS nusb's device-level control
      7 //! transfers require claiming the device (or an EP0-bearing interface), and both
      8 //! are blocked here: AppleUSBAudio is attached to the audio interfaces, and
      9 //! FocusriteControlServer holds the vendor interface. rusb's read_control /
     10 //! write_control hit EP0 with no claim — proven on the real 18i20 by the spike.
     11 
     12 use std::time::Duration;
     13 
     14 use rusb::{Direction, Recipient, RequestType, UsbContext};
     15 
     16 use crate::packet::{self, PacketError, Response, HEADER_LEN};
     17 
     18 /// Focusrite / Novation USB vendor id.
     19 pub const VID: u16 = 0x1235;
     20 /// Scarlett 18i20 3rd Gen product id.
     21 pub const PID_18I20_G3: u16 = 0x8215;
     22 
     23 /// The scarlett2 control protocol lives on this (vendor-specific, class 0xFF)
     24 /// interface — confirmed by the Phase-0 spike. Used as the control-transfer
     25 /// `wIndex`.
     26 pub const CONTROL_INTERFACE: u16 = 3;
     27 
     28 const REQ_CMD: u8 = 2; // bRequest: host -> device
     29 const RESP_CMD: u8 = 3; // bRequest: device -> host
     30 const TIMEOUT: Duration = Duration::from_millis(1000);
     31 /// The IN read can time out when the device defers a larger response (it normally
     32 /// signals readiness on the interrupt EP, which we can't claim on macOS), so we
     33 /// retry the read a few times before giving up.
     34 const READ_ATTEMPTS: u32 = 4;
     35 
     36 #[derive(Debug)]
     37 pub enum TransportError {
     38     /// No matching device is connected.
     39     NotFound,
     40     /// A USB-layer failure (open/transfer); message is from the OS/libusb.
     41     Usb(String),
     42     /// The response packet was malformed or the device returned an error code.
     43     Packet(PacketError),
     44 }
     45 
     46 impl core::fmt::Display for TransportError {
     47     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
     48         match self {
     49             TransportError::NotFound => write!(f, "device not found"),
     50             TransportError::Usb(m) => write!(f, "usb error: {m}"),
     51             TransportError::Packet(e) => write!(f, "protocol error: {e}"),
     52         }
     53     }
     54 }
     55 
     56 impl std::error::Error for TransportError {}
     57 
     58 impl From<PacketError> for TransportError {
     59     fn from(e: PacketError) -> Self {
     60         TransportError::Packet(e)
     61     }
     62 }
     63 
     64 /// One scarlett2 round-trip plus the small amount of state (sequence number) the
     65 /// protocol requires.
     66 pub trait Transport {
     67     /// Send `cmd` with `payload` and return the validated response, reading up to
     68     /// `resp_cap` payload bytes back.
     69     fn command(
     70         &mut self,
     71         cmd: u32,
     72         payload: &[u8],
     73         resp_cap: usize,
     74     ) -> Result<Response, TransportError>;
     75 
     76     /// Raw control-IN with an arbitrary `bRequest` (the INIT "step 0" priming
     77     /// read uses bRequest 0, outside the packet framing).
     78     fn raw_in(&mut self, request: u8, len: usize) -> Result<Vec<u8>, TransportError>;
     79 
     80     /// Force the next sequence number (the INIT_1/INIT_2 handshake resets it to 1).
     81     fn set_seq(&mut self, seq: u16);
     82 }
     83 
     84 /// Live USB transport over endpoint 0 (rusb / libusb).
     85 pub struct UsbTransport {
     86     handle: rusb::DeviceHandle<rusb::Context>,
     87     index: u16,
     88     seq: u16,
     89 }
     90 
     91 impl UsbTransport {
     92     /// Open the first connected Scarlett 18i20 3rd Gen.
     93     pub fn open_default() -> Result<Self, TransportError> {
     94         Self::open(VID, PID_18I20_G3, CONTROL_INTERFACE)
     95     }
     96 
     97     /// Open a specific device + control interface.
     98     pub fn open(vid: u16, pid: u16, index: u16) -> Result<Self, TransportError> {
     99         let ctx = rusb::Context::new().map_err(|e| TransportError::Usb(e.to_string()))?;
    100         let device = ctx
    101             .devices()
    102             .map_err(|e| TransportError::Usb(e.to_string()))?
    103             .iter()
    104             .find(|d| {
    105                 d.device_descriptor()
    106                     .map(|x| x.vendor_id() == vid && x.product_id() == pid)
    107                     .unwrap_or(false)
    108             })
    109             .ok_or(TransportError::NotFound)?;
    110         let handle = device
    111             .open()
    112             .map_err(|e| TransportError::Usb(format!("{e} (quit Focusrite Control?)")))?;
    113         Ok(Self { handle, index, seq: 1 })
    114     }
    115 
    116     fn rt_out() -> u8 {
    117         rusb::request_type(Direction::Out, RequestType::Class, Recipient::Interface)
    118     }
    119 
    120     fn rt_in() -> u8 {
    121         rusb::request_type(Direction::In, RequestType::Class, Recipient::Interface)
    122     }
    123 }
    124 
    125 impl Transport for UsbTransport {
    126     fn command(
    127         &mut self,
    128         cmd: u32,
    129         payload: &[u8],
    130         resp_cap: usize,
    131     ) -> Result<Response, TransportError> {
    132         let seq = self.seq;
    133         self.seq = self.seq.wrapping_add(1);
    134 
    135         let req = packet::encode_request(cmd, seq, payload);
    136         self.handle
    137             .write_control(Self::rt_out(), REQ_CMD, 0, self.index, &req, TIMEOUT)
    138             .map_err(|e| TransportError::Usb(e.to_string()))?;
    139 
    140         let mut buf = vec![0u8; HEADER_LEN + resp_cap];
    141         let mut last = None;
    142         for _ in 0..READ_ATTEMPTS {
    143             match self
    144                 .handle
    145                 .read_control(Self::rt_in(), RESP_CMD, 0, self.index, &mut buf, TIMEOUT)
    146             {
    147                 Ok(n) => {
    148                     buf.truncate(n);
    149                     return Ok(packet::decode_response(cmd, &buf)?);
    150                 }
    151                 Err(rusb::Error::Timeout) => last = Some("read timed out".to_string()),
    152                 Err(e) => last = Some(e.to_string()),
    153             }
    154         }
    155         Err(TransportError::Usb(last.unwrap_or_else(|| "read failed".into())))
    156     }
    157 
    158     fn raw_in(&mut self, request: u8, len: usize) -> Result<Vec<u8>, TransportError> {
    159         let mut buf = vec![0u8; len];
    160         let n = self
    161             .handle
    162             .read_control(Self::rt_in(), request, 0, self.index, &mut buf, TIMEOUT)
    163             .map_err(|e| TransportError::Usb(e.to_string()))?;
    164         buf.truncate(n);
    165         Ok(buf)
    166     }
    167 
    168     fn set_seq(&mut self, seq: u16) {
    169         self.seq = seq;
    170     }
    171 }
    172 
    173 #[cfg(test)]
    174 pub(crate) mod mock {
    175     use super::*;
    176     use std::collections::VecDeque;
    177 
    178     /// Records what the protocol layer sends and replays canned raw response
    179     /// buffers, so protocol logic can be tested without hardware.
    180     pub struct MockTransport {
    181         /// Raw response buffers (header + payload) to return, in order.
    182         pub responses: VecDeque<Vec<u8>>,
    183         /// Every (cmd, payload) the protocol layer sent.
    184         pub sent: Vec<(u32, Vec<u8>)>,
    185         pub seq: u16,
    186         /// When true, any command with no queued response gets a synthesized
    187         /// empty success echo. Handy for write-heavy flows (e.g. apply_preset)
    188         /// where only the `sent` log matters.
    189         pub auto_echo: bool,
    190     }
    191 
    192     impl MockTransport {
    193         pub fn new() -> Self {
    194             Self { responses: VecDeque::new(), sent: Vec::new(), seq: 1, auto_echo: false }
    195         }
    196 
    197         /// A mock that auto-echoes an empty success for every command.
    198         pub fn echoing() -> Self {
    199             Self { auto_echo: true, ..Self::new() }
    200         }
    201 
    202         /// Queue a response with the given cmd echoed and `payload` attached.
    203         pub fn push_response(&mut self, cmd: u32, payload: &[u8]) {
    204             let mut b = Vec::new();
    205             b.extend_from_slice(&cmd.to_le_bytes());
    206             b.extend_from_slice(&(payload.len() as u16).to_le_bytes());
    207             b.extend_from_slice(&self.seq.to_le_bytes());
    208             b.extend_from_slice(&0u32.to_le_bytes()); // error
    209             b.extend_from_slice(&0u32.to_le_bytes()); // pad
    210             b.extend_from_slice(payload);
    211             self.responses.push_back(b);
    212         }
    213     }
    214 
    215     impl Transport for MockTransport {
    216         fn command(
    217             &mut self,
    218             cmd: u32,
    219             payload: &[u8],
    220             _resp_cap: usize,
    221         ) -> Result<Response, TransportError> {
    222             self.sent.push((cmd, payload.to_vec()));
    223             if let Some(raw) = self.responses.pop_front() {
    224                 return Ok(packet::decode_response(cmd, &raw)?);
    225             }
    226             if self.auto_echo {
    227                 // Synthesize an empty matching response.
    228                 let mut b = Vec::new();
    229                 b.extend_from_slice(&cmd.to_le_bytes());
    230                 b.extend_from_slice(&0u16.to_le_bytes()); // size
    231                 b.extend_from_slice(&self.seq.to_le_bytes());
    232                 b.extend_from_slice(&0u32.to_le_bytes()); // error
    233                 b.extend_from_slice(&0u32.to_le_bytes()); // pad
    234                 return Ok(packet::decode_response(cmd, &b)?);
    235             }
    236             panic!("MockTransport: no queued response for cmd {cmd:#x}");
    237         }
    238 
    239         fn raw_in(&mut self, _request: u8, _len: usize) -> Result<Vec<u8>, TransportError> {
    240             Ok(Vec::new())
    241         }
    242 
    243         fn set_seq(&mut self, seq: u16) {
    244             self.seq = seq;
    245         }
    246     }
    247 }