usbip-rs/cli/src/test_hid.rs
Davíð Steinn Geirsson 4c368c02b5 feat: concurrent ISO pipelining via nusb update and &self handlers
Update nusb to c1380673 which allows multiple IsoEndpoint instances per
address, enabling concurrent URB submission from separate threads.

Change UsbInterfaceHandler trait methods from &mut self to &self and
replace Arc<Mutex<Box<dyn Handler>>> with Arc<dyn Handler>. This
removes the serialization bottleneck where the handler mutex was held
for the entire USB transfer duration, causing ISO audio to play at
~67% speed.

Handlers needing interior mutability (HID, CDC) now use Mutex on
individual fields. Passthrough handlers already used Arc<Mutex<>>
internally and need no changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 15:10:28 +00:00

81 lines
2.6 KiB
Rust

use log::info;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use usbip_rs::{
ClassCode, UsbDevice, UsbEndpoint, UsbInterfaceHandler, hid::UsbHidKeyboardHandler,
usbip_protocol::UsbIpResponse,
};
use crate::transport;
/// Send handshake, spawn key simulator, and run URB loop.
async fn do_test_hid_session<S: AsyncReadExt + AsyncWriteExt + Unpin + Send + 'static>(
mut stream: S,
device: UsbDevice,
handler: Arc<UsbHidKeyboardHandler>,
) -> std::io::Result<()> {
// Send device info (simplified handshake)
let handshake = UsbIpResponse::op_rep_import_success(&device).to_bytes()?;
stream
.write_all(&handshake)
.await
.map_err(|e| std::io::Error::other(format!("Failed to send handshake: {e}")))?;
info!("Handshake sent, entering URB loop");
// Spawn key event simulator
let handler_clone = handler.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
match usbip_rs::hid::UsbHidKeyboardReport::from_ascii(b'1') {
Ok(report) => {
handler_clone.pending_key_events.lock().unwrap().push_back(report);
info!("Simulated key event '1'");
}
Err(e) => {
info!("Failed to create key report: {}", e);
}
}
}
});
// Handle URBs
usbip_rs::handle_urb_loop(stream, Arc::new(device)).await
}
pub async fn run(addr: transport::TransportAddr) -> std::io::Result<()> {
// Create simulated HID keyboard
let handler = Arc::new(UsbHidKeyboardHandler::new_keyboard());
let device = UsbDevice::new(0)?.with_interface(
ClassCode::HID as u8,
0x00,
0x00,
Some("Test HID Keyboard"),
vec![UsbEndpoint {
address: 0x81, // IN
attributes: 0x03, // Interrupt
max_packet_size: 0x08, // 8 bytes
interval: 10,
}],
handler.clone() as Arc<dyn UsbInterfaceHandler>,
)?;
info!(
"Created simulated HID keyboard {:04x}:{:04x}",
device.vendor_id, device.product_id
);
// Connect via transport and run session
match addr {
transport::TransportAddr::Vsock(v) => {
let stream = transport::connect_vsock(v.cid, v.port).await?;
do_test_hid_session(stream, device, handler).await
}
transport::TransportAddr::Tcp(ref t) => {
let stream = transport::connect_tcp(t).await?;
do_test_hid_session(stream, device, handler).await
}
}
}