Skip to main content

viva_u3v/
device.rs

1//! High-level USB3 Vision device handle.
2//!
3//! [`U3vDevice`] combines a control channel with parsed bootstrap registers
4//! to provide register I/O, XML fetching, and stream configuration.
5
6use std::io::Read;
7use std::sync::Arc;
8
9use crate::U3vError;
10use crate::bootstrap::{Abrm, ManifestEntry, Sbrm, Sirm};
11use crate::control::ControlChannel;
12use crate::usb::UsbTransfer;
13
14/// ZIP local file header magic: `PK\x03\x04`.
15const ZIP_MAGIC: &[u8; 4] = b"PK\x03\x04";
16
17/// If `data` starts with a ZIP signature, extract the first file's contents.
18/// Otherwise return `data` unchanged.
19fn decompress_if_zip(data: Vec<u8>) -> Result<Vec<u8>, U3vError> {
20    if data.len() < 4 || &data[..4] != ZIP_MAGIC {
21        return Ok(data);
22    }
23    let cursor = std::io::Cursor::new(&data);
24    let mut archive =
25        zip::ZipArchive::new(cursor).map_err(|e| U3vError::Protocol(format!("bad ZIP: {e}")))?;
26    if archive.is_empty() {
27        return Err(U3vError::Protocol("ZIP archive is empty".into()));
28    }
29    let mut file = archive
30        .by_index(0)
31        .map_err(|e| U3vError::Protocol(format!("cannot read ZIP entry: {e}")))?;
32    let mut xml = Vec::with_capacity(file.size() as usize);
33    file.read_to_end(&mut xml)
34        .map_err(|e| U3vError::Protocol(format!("ZIP decompression failed: {e}")))?;
35    Ok(xml)
36}
37
38/// Default maximum command/ack transfer size used before reading SBRM.
39///
40/// The USB3 Vision spec guarantees at least 1024 bytes for the initial
41/// bootstrap reads.
42const INITIAL_MAX_TRANSFER: u32 = 1024;
43
44/// High-level handle for a USB3 Vision device.
45///
46/// Wraps a [`ControlChannel`] and parsed bootstrap registers. The generic
47/// `T` parameter allows production use with [`RusbTransfer`](crate::usb::RusbTransfer)
48/// and testing with [`MockUsbTransfer`](crate::usb::MockUsbTransfer).
49pub struct U3vDevice<T: UsbTransfer> {
50    control: ControlChannel<T>,
51    abrm: Abrm,
52    sbrm: Sbrm,
53    stream_ep: Option<u8>,
54    event_ep: Option<u8>,
55}
56
57impl<T: UsbTransfer> U3vDevice<T> {
58    /// Open a U3V device given a shared USB transport and endpoint addresses.
59    ///
60    /// Reads the ABRM and SBRM bootstrap registers, then re-creates the
61    /// control channel with the device-reported maximum transfer sizes.
62    pub fn open(
63        transport: Arc<T>,
64        ep_in: u8,
65        ep_out: u8,
66        stream_ep: Option<u8>,
67        event_ep: Option<u8>,
68    ) -> Result<Self, U3vError> {
69        // Bootstrap with conservative transfer limits.
70        let mut control = ControlChannel::new(
71            Arc::clone(&transport),
72            ep_in,
73            ep_out,
74            INITIAL_MAX_TRANSFER,
75            INITIAL_MAX_TRANSFER,
76        );
77
78        let abrm = Abrm::read_from(&mut control)?;
79        let sbrm = Sbrm::read_from(&mut control, abrm.sbrm_address)?;
80
81        // Re-create the control channel with the actual device limits.
82        let control = ControlChannel::new(
83            transport,
84            ep_in,
85            ep_out,
86            sbrm.max_cmd_transfer,
87            sbrm.max_ack_transfer,
88        );
89
90        Ok(Self {
91            control,
92            abrm,
93            sbrm,
94            stream_ep,
95            event_ep,
96        })
97    }
98
99    /// Access the shared transport handle (e.g. to create additional streams).
100    pub fn transport(&self) -> Arc<T> {
101        self.control.transport().clone()
102    }
103
104    /// Reference to the parsed ABRM.
105    pub fn abrm(&self) -> &Abrm {
106        &self.abrm
107    }
108
109    /// Reference to the parsed SBRM.
110    pub fn sbrm(&self) -> &Sbrm {
111        &self.sbrm
112    }
113
114    /// Read `len` bytes from device memory at `addr`.
115    pub fn read_mem(&mut self, addr: u64, len: usize) -> Result<Vec<u8>, U3vError> {
116        self.control.read_mem(addr, len)
117    }
118
119    /// Write `data` to device memory at `addr`.
120    pub fn write_mem(&mut self, addr: u64, data: &[u8]) -> Result<(), U3vError> {
121        self.control.write_mem(addr, data)
122    }
123
124    /// Read the SIRM (Streaming Interface Register Map) from the device.
125    pub fn read_sirm(&mut self) -> Result<Sirm, U3vError> {
126        Sirm::read_from(&mut self.control, self.sbrm.sirm_address)
127    }
128
129    /// Fetch the GenICam XML descriptor from the device's manifest table.
130    ///
131    /// If the manifest payload is ZIP-compressed (starts with `PK\x03\x04`),
132    /// it is decompressed transparently. Returns the XML as a UTF-8 string.
133    pub fn fetch_xml(&mut self) -> Result<String, U3vError> {
134        let entry = ManifestEntry::read_first(&mut self.control, self.abrm.manifest_table_address)?;
135        let raw = self
136            .control
137            .read_mem(entry.file_address, entry.file_size as usize)?;
138        let xml_bytes = decompress_if_zip(raw)?;
139        String::from_utf8(xml_bytes)
140            .map_err(|e| U3vError::Protocol(format!("XML is not valid UTF-8: {e}")))
141    }
142
143    /// The bulk IN endpoint for streaming data, if the device has one.
144    pub fn stream_ep(&self) -> Option<u8> {
145        self.stream_ep
146    }
147
148    /// The bulk IN endpoint for events, if the device has one.
149    pub fn event_ep(&self) -> Option<u8> {
150        self.event_ep
151    }
152
153    /// Read the SIRM, configure stream sizes, and create a [`crate::stream::U3vStream`]
154    /// for receiving frames.
155    ///
156    /// The stream endpoint must have been discovered during device open.
157    /// Configures the SIRM with the device's maximum leader/trailer sizes
158    /// and the specified payload size, then enables streaming.
159    pub fn open_stream(
160        &mut self,
161        payload_size: u64,
162    ) -> Result<crate::stream::U3vStream<T>, U3vError> {
163        let ep = self
164            .stream_ep
165            .ok_or_else(|| U3vError::Protocol("device has no streaming endpoint".into()))?;
166
167        let sirm = self.read_sirm()?;
168        sirm.configure(
169            &mut self.control,
170            payload_size,
171            sirm.max_leader_size,
172            sirm.max_trailer_size,
173        )?;
174        sirm.enable(&mut self.control)?;
175
176        Ok(crate::stream::U3vStream::new(
177            self.control.transport().clone(),
178            ep,
179            sirm.max_leader_size as usize,
180            sirm.max_trailer_size as usize,
181            payload_size as usize,
182        ))
183    }
184
185    /// Disable streaming via the SIRM control register.
186    pub fn stop_stream(&mut self) -> Result<(), U3vError> {
187        let sirm = self.read_sirm()?;
188        sirm.disable(&mut self.control)
189    }
190}
191
192// ---------------------------------------------------------------------------
193// Open from rusb device (convenience)
194// ---------------------------------------------------------------------------
195
196#[cfg(feature = "usb")]
197impl U3vDevice<crate::usb::RusbTransfer> {
198    /// Open a USB3 Vision device from a [`U3vDeviceInfo`](crate::discovery::U3vDeviceInfo)
199    /// obtained via [`discover()`](crate::discovery::discover).
200    pub fn open_device(info: &crate::discovery::U3vDeviceInfo) -> Result<Self, U3vError> {
201        use rusb::UsbContext;
202        let context = rusb::Context::new().map_err(|e| U3vError::Usb(e.to_string()))?;
203        let devices = context
204            .devices()
205            .map_err(|e| U3vError::Usb(e.to_string()))?;
206
207        let device = devices
208            .iter()
209            .find(|d| d.bus_number() == info.bus && d.address() == info.address)
210            .ok_or_else(|| {
211                U3vError::Usb(format!(
212                    "device not found at bus={} addr={}",
213                    info.bus, info.address
214                ))
215            })?;
216
217        let handle = device.open().map_err(|e| U3vError::Usb(e.to_string()))?;
218
219        // Claim all U3V interfaces.
220        let iface = &info.interface_info;
221        handle
222            .claim_interface(iface.control_iface)
223            .map_err(|e| U3vError::Usb(format!("claim control interface: {e}")))?;
224        if let Some(si) = iface.stream_iface {
225            let _ = handle.claim_interface(si);
226        }
227        if let Some(ei) = iface.event_iface {
228            let _ = handle.claim_interface(ei);
229        }
230
231        let transport = Arc::new(crate::usb::RusbTransfer::new(Arc::new(handle)));
232        Self::open(
233            transport,
234            iface.control_ep_in,
235            iface.control_ep_out,
236            iface.stream_ep_in,
237            iface.event_ep_in,
238        )
239    }
240}
241
242// ---------------------------------------------------------------------------
243// Tests
244// ---------------------------------------------------------------------------
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::usb::MockUsbTransfer;
250    use bytes::{BufMut, BytesMut};
251
252    const EP_OUT: u8 = 0x01;
253    const EP_IN: u8 = 0x81;
254    const ACK_PREFIX_LE: u32 = 0x4356_3341;
255    const PREFIX_SIZE: usize = 12;
256
257    fn success_ack(request_id: u16, payload: &[u8]) -> Vec<u8> {
258        let mut buf = BytesMut::with_capacity(PREFIX_SIZE + payload.len());
259        buf.put_u32_le(ACK_PREFIX_LE);
260        buf.put_u16_le(0x0000); // Success
261        buf.put_u16_le(0x0085); // ReadMem ack
262        buf.put_u16_le(payload.len() as u16);
263        buf.put_u16_le(request_id);
264        buf.extend_from_slice(payload);
265        buf.to_vec()
266    }
267
268    /// Enqueue all the ack responses needed for Abrm::read_from + Sbrm::read_from.
269    /// Returns the next request_id.
270    fn enqueue_bootstrap_responses(mock: &MockUsbTransfer, sbrm_addr: u64) -> u16 {
271        let mut req: u16 = 0;
272
273        // -- ABRM reads --
274        // gencp_version (4 bytes)
275        mock.enqueue_read(EP_IN, success_ack(req, &0x0001_0000u32.to_be_bytes()));
276        req += 1;
277        // manufacturer_name (64 bytes)
278        let mut s = vec![0u8; 64];
279        s[..4].copy_from_slice(b"Test");
280        mock.enqueue_read(EP_IN, success_ack(req, &s));
281        req += 1;
282        // model_name
283        s = vec![0u8; 64];
284        s[..7].copy_from_slice(b"MockCam");
285        mock.enqueue_read(EP_IN, success_ack(req, &s));
286        req += 1;
287        // family_name
288        mock.enqueue_read(EP_IN, success_ack(req, &[0u8; 64]));
289        req += 1;
290        // device_version
291        mock.enqueue_read(EP_IN, success_ack(req, &[0u8; 64]));
292        req += 1;
293        // serial_number
294        mock.enqueue_read(EP_IN, success_ack(req, &[0u8; 64]));
295        req += 1;
296        // user_defined_name
297        mock.enqueue_read(EP_IN, success_ack(req, &[0u8; 64]));
298        req += 1;
299        // manifest_table_address (8 bytes)
300        mock.enqueue_read(EP_IN, success_ack(req, &0x0005_0000u64.to_be_bytes()));
301        req += 1;
302        // sbrm_address (8 bytes)
303        mock.enqueue_read(EP_IN, success_ack(req, &sbrm_addr.to_be_bytes()));
304        req += 1;
305        // device_capability (8 bytes)
306        mock.enqueue_read(EP_IN, success_ack(req, &0u64.to_be_bytes()));
307        req += 1;
308
309        // -- SBRM reads --
310        // u3v_version
311        mock.enqueue_read(EP_IN, success_ack(req, &0x0001_0000u32.to_be_bytes()));
312        req += 1;
313        // max_cmd_transfer
314        mock.enqueue_read(EP_IN, success_ack(req, &2048u32.to_be_bytes()));
315        req += 1;
316        // max_ack_transfer
317        mock.enqueue_read(EP_IN, success_ack(req, &2048u32.to_be_bytes()));
318        req += 1;
319        // num_stream_channels
320        mock.enqueue_read(EP_IN, success_ack(req, &1u32.to_be_bytes()));
321        req += 1;
322        // sirm_address
323        mock.enqueue_read(EP_IN, success_ack(req, &0x0002_0000u64.to_be_bytes()));
324        req += 1;
325        // sirm_length
326        mock.enqueue_read(EP_IN, success_ack(req, &256u32.to_be_bytes()));
327        req += 1;
328        // eirm_address
329        mock.enqueue_read(EP_IN, success_ack(req, &0x0003_0000u64.to_be_bytes()));
330        req += 1;
331        // eirm_length
332        mock.enqueue_read(EP_IN, success_ack(req, &64u32.to_be_bytes()));
333        req += 1;
334
335        req
336    }
337
338    #[test]
339    fn open_device_reads_bootstrap() {
340        let mock = Arc::new(MockUsbTransfer::new());
341        let sbrm_addr: u64 = 0x0001_0000;
342        enqueue_bootstrap_responses(&mock, sbrm_addr);
343
344        let dev = U3vDevice::open(Arc::clone(&mock), EP_IN, EP_OUT, Some(0x82), None).unwrap();
345
346        assert_eq!(dev.abrm().manufacturer_name, "Test");
347        assert_eq!(dev.sbrm().max_cmd_transfer, 2048);
348        assert_eq!(dev.sbrm().max_ack_transfer, 2048);
349        assert_eq!(dev.sbrm().num_stream_channels, 1);
350        assert_eq!(dev.stream_ep(), Some(0x82));
351        assert_eq!(dev.event_ep(), None);
352    }
353
354    #[test]
355    fn fetch_xml_from_manifest() {
356        let mock = Arc::new(MockUsbTransfer::new());
357        let sbrm_addr: u64 = 0x0001_0000;
358        enqueue_bootstrap_responses(&mock, sbrm_addr);
359
360        let mut dev = U3vDevice::open(Arc::clone(&mock), EP_IN, EP_OUT, None, None).unwrap();
361
362        // After open(), the control channel is re-created with request_id = 0.
363        let mut req: u16 = 0;
364
365        // Manifest table: count = 1
366        mock.enqueue_read(EP_IN, success_ack(req, &1u32.to_be_bytes()));
367        req += 1;
368
369        // Manifest entry: [8 info][8 addr][8 size]
370        let xml_addr: u64 = 0x0010_0000;
371        let xml_content = b"<RegisterDescription />";
372        let xml_size = xml_content.len() as u64;
373        let mut entry_data = BytesMut::with_capacity(24);
374        entry_data.put_u64(0); // file info
375        entry_data.put_u64(xml_addr);
376        entry_data.put_u64(xml_size);
377        mock.enqueue_read(EP_IN, success_ack(req, &entry_data));
378        req += 1;
379
380        // XML content
381        mock.enqueue_read(EP_IN, success_ack(req, xml_content));
382
383        let xml = dev.fetch_xml().unwrap();
384        assert_eq!(xml, "<RegisterDescription />");
385    }
386}