refactor: remove rusb backend, keep only nusb

- Delete lib/examples/ (unused upstream leftovers)
- Remove rusb crate dependency
- Define local Direction enum in consts.rs (Out=0x00, In=0x80)
- Remove RusbUsbHostInterfaceHandler and RusbUsbHostDeviceHandler
- Replace rusb device enumeration with nusb in new_from_host*
- Update flake.nix cargoHash and Cargo.lock

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Davíð Steinn Geirsson 2026-03-25 15:19:51 +00:00
parent 80083fdf93
commit e0aa6bfbf6
11 changed files with 16 additions and 546 deletions

58
Cargo.lock generated
View file

@ -103,16 +103,6 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@ -220,12 +210,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "futures"
version = "0.3.32"
@ -366,18 +350,6 @@ version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "libusb1-sys"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@ -525,12 +497,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.13.1"
@ -602,17 +568,6 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "rusb"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4"
dependencies = [
"libc",
"libusb1-sys",
"serde",
]
[[package]]
name = "rustix"
version = "1.1.4"
@ -662,12 +617,6 @@ dependencies = [
"syn",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
@ -787,7 +736,6 @@ dependencies = [
"num-derive",
"num-traits",
"nusb",
"rusb",
"serde",
"tokio",
"tokio-util",
@ -812,12 +760,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vsock"
version = "0.5.3"

View file

@ -31,7 +31,7 @@
src = self;
cargoHash = "sha256-ol61Q+2IYDZ4ZvJiLYJdfqaN23thuZ1QHDOSvNXHhvY=";
cargoHash = "sha256-i2dRJ7n7J9K2PfjEUnIPS9pRXs61WUYYXAfJUmFxt/M=";
inherit nativeBuildInputs buildInputs;

View file

@ -12,7 +12,6 @@ tokio = { version = "1.22.0", features = ["rt", "net", "io-util", "sync", "time"
log = "0.4.17"
num-traits = "0.2.15"
num-derive = "0.4.2"
rusb = "0.9.3"
serde = { version = "1.0", features = ["derive"], optional = true }
nusb = { git = "https://git.dsg.is/dsg/nusb.git", rev = "c1380673" }
tokio-util = { version = "0.7", features = ["rt"] }
@ -23,4 +22,4 @@ env_logger = "0.11.7"
[features]
default = []
serde = ["dep:serde", "rusb/serde"]
serde = ["dep:serde"]

View file

@ -1,32 +0,0 @@
use log::*;
use std::net::*;
use std::sync::Arc;
use std::time::Duration;
#[tokio::main]
async fn main() {
env_logger::init();
let handler = Arc::new(usbip_rs::cdc::UsbCdcAcmHandler::new());
let server = Arc::new(usbip_rs::UsbIpServer::new_simulated(vec![
usbip_rs::UsbDevice::new(0)
.expect("create device")
.with_interface(
usbip_rs::ClassCode::CDC as u8,
usbip_rs::cdc::CDC_ACM_SUBCLASS,
0x00,
Some("Test CDC ACM"),
usbip_rs::cdc::UsbCdcAcmHandler::endpoints(),
handler.clone() as Arc<dyn usbip_rs::UsbInterfaceHandler>,
)
.expect("add interface"),
]));
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3240);
tokio::spawn(usbip_rs::server(addr, server));
loop {
// sleep 1s
tokio::time::sleep(Duration::new(1, 0)).await;
handler.tx_buffer.lock().unwrap().push(b'a');
info!("Simulate a char input");
}
}

View file

@ -1,45 +0,0 @@
use log::*;
use std::net::*;
use std::sync::Arc;
use std::time::Duration;
#[tokio::main]
async fn main() {
env_logger::init();
let handler = Arc::new(usbip_rs::hid::UsbHidKeyboardHandler::new_keyboard());
let server = Arc::new(usbip_rs::UsbIpServer::new_simulated(vec![
usbip_rs::UsbDevice::new(0)
.expect("create device")
.with_interface(
usbip_rs::ClassCode::HID as u8,
0x00,
0x00,
Some("Test HID"),
vec![usbip_rs::UsbEndpoint {
address: 0x81, // IN
attributes: 0x03, // Interrupt
max_packet_size: 0x08, // 8 bytes
interval: 10,
..Default::default()
}],
handler.clone() as Arc<dyn usbip_rs::UsbInterfaceHandler>,
)
.expect("add interface"),
]));
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3240);
tokio::spawn(usbip_rs::server(addr, server));
loop {
// sleep 1s
tokio::time::sleep(Duration::new(1, 0)).await;
match usbip_rs::hid::UsbHidKeyboardReport::from_ascii(b'1') {
Ok(report) => {
handler.pending_key_events.lock().unwrap().push_back(report);
info!("Simulate a key event");
}
Err(e) => {
error!("Failed to create key report: {}", e);
}
}
}
}

View file

@ -1,16 +0,0 @@
use std::net::*;
use std::sync::Arc;
use std::time::Duration;
#[tokio::main]
async fn main() {
env_logger::init();
let server = Arc::new(usbip_rs::UsbIpServer::new_from_host());
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3240);
tokio::spawn(usbip_rs::server(addr, server));
loop {
// sleep 1s
tokio::time::sleep(Duration::new(1, 0)).await;
}
}

View file

@ -52,9 +52,13 @@ pub enum EndpointAttributes {
Interrupt,
}
/// USB endpoint direction: IN or OUT
/// Already exists in rusb crate
pub use rusb::Direction;
/// USB endpoint direction (bit 7 of endpoint address)
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Direction {
Out = 0x00,
In = 0x80,
}
/// Emulated max packet size of EP0
pub const EP0_MAX_PACKET_SIZE: u16 = 64;

View file

@ -1,6 +1,4 @@
use super::*;
use rusb::Version as rusbVersion;
#[derive(Clone, Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Version {
@ -9,22 +7,6 @@ pub struct Version {
pub patch: u8,
}
impl From<rusbVersion> for Version {
fn from(value: rusbVersion) -> Self {
Self {
major: value.major(),
minor: value.minor(),
patch: value.sub_minor(),
}
}
}
impl From<Version> for rusbVersion {
fn from(val: Version) -> Self {
rusbVersion(val.major, val.minor, val.patch)
}
}
/// bcdDevice
impl From<u16> for Version {
fn from(value: u16) -> Self {

View file

@ -17,180 +17,6 @@ pub fn nusb_speed_to_linux(speed: nusb::Speed) -> u32 {
}
}
/// A handler to pass requests to interface of a rusb USB device of the host
#[derive(Clone, Debug)]
pub struct RusbUsbHostInterfaceHandler {
handle: Arc<Mutex<DeviceHandle<GlobalContext>>>,
}
impl RusbUsbHostInterfaceHandler {
pub fn new(handle: Arc<Mutex<DeviceHandle<GlobalContext>>>) -> Self {
Self { handle }
}
}
impl UsbInterfaceHandler for RusbUsbHostInterfaceHandler {
fn handle_urb(
&self,
_interface: &UsbInterface,
request: UrbRequest,
) -> Result<UrbResponse> {
let ep = request.ep.clone();
let transfer_buffer_length = request.transfer_buffer_length;
let setup = request.setup;
let req = &request.data;
debug!("To host device: ep={ep:?} setup={setup:?} req={req:?}",);
let mut buffer = vec![0u8; transfer_buffer_length as usize];
let timeout = std::time::Duration::new(1, 0);
let handle = self.handle.lock().unwrap_or_else(|e| e.into_inner());
if ep.transfer_type() == Some(EndpointAttributes::Control) {
// control
if let Direction::In = ep.direction() {
// control in
if let Ok(len) = handle.read_control(
setup.request_type,
setup.request,
setup.value,
setup.index,
&mut buffer,
timeout,
) {
return Ok(UrbResponse { data: Vec::from(&buffer[..len]), ..Default::default() });
}
} else {
// control out
handle
.write_control(
setup.request_type,
setup.request,
setup.value,
setup.index,
req,
timeout,
)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("USB control OUT failed: {e}"),
)
})?;
}
} else if ep.transfer_type() == Some(EndpointAttributes::Interrupt) {
// interrupt
if let Direction::In = ep.direction() {
// interrupt in
if let Ok(len) = handle.read_interrupt(ep.address, &mut buffer, timeout) {
info!("intr in {:?}", &buffer[..len]);
return Ok(UrbResponse { data: Vec::from(&buffer[..len]), ..Default::default() });
}
} else {
// interrupt out
handle
.write_interrupt(ep.address, req, timeout)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("USB interrupt OUT failed: {e}"),
)
})?;
}
} else if ep.transfer_type() == Some(EndpointAttributes::Bulk) {
// bulk
if let Direction::In = ep.direction() {
// bulk in
if let Ok(len) = handle.read_bulk(ep.address, &mut buffer, timeout) {
return Ok(UrbResponse { data: Vec::from(&buffer[..len]), ..Default::default() });
}
} else {
// bulk out
handle.write_bulk(ep.address, req, timeout).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("USB bulk OUT failed: {e}"),
)
})?;
}
} else if ep.transfer_type() == Some(EndpointAttributes::Isochronous) {
return Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"isochronous transfers not supported on rusb handler",
));
}
Ok(UrbResponse::default())
}
fn get_class_specific_descriptor(&self) -> Vec<u8> {
vec![]
}
fn as_any(&self) -> &dyn Any {
self
}
}
/// A handler to pass requests to device of a rusb USB device of the host
#[derive(Clone, Debug)]
pub struct RusbUsbHostDeviceHandler {
handle: Arc<Mutex<DeviceHandle<GlobalContext>>>,
}
impl RusbUsbHostDeviceHandler {
pub fn new(handle: Arc<Mutex<DeviceHandle<GlobalContext>>>) -> Self {
Self { handle }
}
}
impl UsbDeviceHandler for RusbUsbHostDeviceHandler {
fn handle_urb(
&mut self,
request: UrbRequest,
) -> Result<UrbResponse> {
let transfer_buffer_length = request.transfer_buffer_length;
let setup = request.setup;
let req = &request.data;
debug!("To host device: setup={setup:?} req={req:?}");
let mut buffer = vec![0u8; transfer_buffer_length as usize];
let timeout = std::time::Duration::new(1, 0);
let handle = self.handle.lock().unwrap_or_else(|e| e.into_inner());
// control
if setup.request_type & 0x80 == 0 {
// control out
handle
.write_control(
setup.request_type,
setup.request,
setup.value,
setup.index,
req,
timeout,
)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("USB control OUT failed: {e}"),
)
})?;
} else {
// control in
if let Ok(len) = handle.read_control(
setup.request_type,
setup.request,
setup.value,
setup.index,
&mut buffer,
timeout,
) {
return Ok(UrbResponse { data: Vec::from(&buffer[..len]), ..Default::default() });
}
}
Ok(UrbResponse::default())
}
fn as_any(&self) -> &dyn Any {
self
}
}
/// A handler to pass requests to interface of a nusb USB device of the host
#[derive(Clone)]
pub struct NusbUsbHostInterfaceHandler {

View file

@ -4,7 +4,6 @@ use log::*;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use nusb::MaybeFuture;
use rusb::*;
use std::any::Any;
use std::collections::{HashMap, VecDeque};
use std::io::{ErrorKind, Result};
@ -187,210 +186,21 @@ impl UsbIpServer {
devices
}
/// Create a [UsbIpServer] with Vec<[rusb::DeviceHandle]> for sharing host devices
pub fn with_rusb_device_handles(
device_handles: Vec<DeviceHandle<GlobalContext>>,
) -> Vec<UsbDevice> {
let mut devices = vec![];
for open_device in device_handles {
let dev = open_device.device();
let desc = match dev.device_descriptor() {
Ok(desc) => desc,
Err(err) => {
warn!(
"Impossible to get device descriptor for {dev:?}: {err}, ignoring device",
);
continue;
}
};
let cfg = match dev.active_config_descriptor() {
Ok(desc) => desc,
Err(err) => {
warn!(
"Impossible to get config descriptor for {dev:?}: {err}, ignoring device",
);
continue;
}
};
let handle = Arc::new(Mutex::new(open_device));
let mut interface_states = vec![];
handle
.lock()
.unwrap()
.set_auto_detach_kernel_driver(true)
.ok();
for intf in cfg.interfaces() {
// ignore alternate settings
let intf_desc = intf.descriptors().next().unwrap();
handle
.lock()
.unwrap()
.set_auto_detach_kernel_driver(true)
.ok();
let mut endpoints = vec![];
for ep_desc in intf_desc.endpoint_descriptors() {
endpoints.push(UsbEndpoint {
address: ep_desc.address(),
attributes: ep_desc.transfer_type() as u8,
max_packet_size: ep_desc.max_packet_size(),
interval: ep_desc.interval(),
..Default::default()
});
}
let handler: Arc<dyn UsbInterfaceHandler> = Arc::new(
RusbUsbHostInterfaceHandler::new(handle.clone()),
);
interface_states.push(InterfaceState::new(UsbInterface {
interface_class: intf_desc.class_code(),
interface_subclass: intf_desc.sub_class_code(),
interface_protocol: intf_desc.protocol_code(),
endpoints,
string_interface: intf_desc.description_string_index().unwrap_or(0),
class_specific_descriptor: Vec::from(intf_desc.extra()),
handler,
}));
}
let mut device = UsbDevice {
path: format!(
"/sys/bus/{}/{}/{}",
dev.bus_number(),
dev.address(),
dev.port_number()
),
bus_id: format!(
"{}-{}-{}",
dev.bus_number(),
dev.address(),
dev.port_number()
),
bus_num: dev.bus_number() as u32,
dev_num: dev.port_number() as u32,
speed: dev.speed() as u32,
vendor_id: desc.vendor_id(),
product_id: desc.product_id(),
device_class: desc.class_code(),
device_subclass: desc.sub_class_code(),
device_protocol: desc.protocol_code(),
device_bcd: desc.device_version().into(),
configuration_value: cfg.number(),
num_configurations: desc.num_configurations(),
ep0_in: UsbEndpoint {
address: 0x80,
attributes: EndpointAttributes::Control as u8,
max_packet_size: desc.max_packet_size() as u16,
..Default::default()
},
ep0_out: UsbEndpoint {
address: 0x00,
attributes: EndpointAttributes::Control as u8,
max_packet_size: desc.max_packet_size() as u16,
..Default::default()
},
interface_states,
device_handler: Some(Arc::new(Mutex::new(Box::new(
RusbUsbHostDeviceHandler::new(handle.clone()),
)))),
usb_version: desc.usb_version().into(),
..UsbDevice::default()
};
// set strings
if let Some(index) = desc.manufacturer_string_index() {
match handle
.lock()
.unwrap()
.read_string_descriptor_ascii(index)
{
Ok(s) => match device.new_string(&s) {
Ok(idx) => device.string_manufacturer = idx,
Err(err) => {
warn!("Failed to set manufacturer string: {err}");
continue;
}
},
Err(err) => {
warn!("Failed to read manufacturer string: {err}");
}
}
}
if let Some(index) = desc.product_string_index() {
match handle
.lock()
.unwrap()
.read_string_descriptor_ascii(index)
{
Ok(s) => match device.new_string(&s) {
Ok(idx) => device.string_product = idx,
Err(err) => {
warn!("Failed to set product string: {err}");
continue;
}
},
Err(err) => {
warn!("Failed to read product string: {err}");
}
}
}
if let Some(index) = desc.serial_number_string_index() {
match handle
.lock()
.unwrap()
.read_string_descriptor_ascii(index)
{
Ok(s) => match device.new_string(&s) {
Ok(idx) => device.string_serial = idx,
Err(err) => {
warn!("Failed to set serial string: {err}");
continue;
}
},
Err(err) => {
warn!("Failed to read serial string: {err}");
}
}
}
devices.push(device);
}
devices
}
fn with_rusb_devices(device_list: Vec<Device<GlobalContext>>) -> Vec<UsbDevice> {
let mut device_handles = vec![];
for dev in device_list {
let open_device = match dev.open() {
Ok(dev) => dev,
Err(err) => {
warn!("Impossible to share {dev:?}: {err}, ignoring device");
continue;
}
};
device_handles.push(open_device);
}
Self::with_rusb_device_handles(device_handles)
}
/// Create a [UsbIpServer] exposing devices in the host, and redirect all USB transfers to them using libusb
/// Create a [UsbIpServer] exposing devices in the host using nusb
pub fn new_from_host() -> Self {
Self::new_from_host_with_filter(|_| true)
}
/// Create a [UsbIpServer] exposing filtered devices in the host, and redirect all USB transfers to them using libusb
/// Create a [UsbIpServer] exposing filtered devices in the host using nusb
pub fn new_from_host_with_filter<F>(filter: F) -> Self
where
F: FnMut(&Device<GlobalContext>) -> bool,
F: FnMut(&nusb::DeviceInfo) -> bool,
{
match rusb::devices() {
match nusb::list_devices().wait() {
Ok(list) => {
let mut devs = vec![];
for d in list.iter().filter(filter) {
devs.push(d)
}
let devs: Vec<_> = list.filter(filter).collect();
Self {
available_devices: RwLock::new(Self::with_rusb_devices(devs)),
available_devices: RwLock::new(Self::with_nusb_devices(devs)),
..Default::default()
}
}

View file

@ -46,7 +46,7 @@ pub const USBIP_RET_UNLINK: u16 = 0x0004;
/// USB/IP direction
///
/// NOTE: Must not be confused with rusb::Direction,
/// NOTE: Must not be confused with the public Direction enum in consts.rs,
/// which has the opposite enum values. This is only for
/// internal use in the USB/IP protocol.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]