GithubHelp home page GithubHelp logo

rusb's Introduction

Rusb

This crate provides a safe wrapper around the native libusb library. It applies the RAII pattern and Rust lifetimes to ensure safe usage of all libusb functionality. The RAII pattern ensures that all acquired resources are released when they're no longer needed, and Rust lifetimes ensure that resources are released in a proper order.

Dependencies

To use rusb no extra setup is required as rusb will automatically download the source for libusb and build it.

However if building libusb fails you can also try setting up the native libusb library where it can be found by pkg-config or vcpkg.

All systems supported by the native libusb library are also supported by the libusb crate. It's been tested on Linux, OS X, and Windows.

Cross-Compiling

The rusb crate can be used when cross-compiling to a foreign target. Details on how to cross-compile rusb are explained in the libusb1-sys crate's README.

Usage

Add rusb as a dependency in Cargo.toml:

[dependencies]
rusb = "0.9"

Import the rusb crate. The starting point for nearly all rusb functionality is to create a context object. With a context object, you can list devices, read their descriptors, open them, and communicate with their endpoints:

fn main() {
    for device in rusb::devices().unwrap().iter() {
        let device_desc = device.device_descriptor().unwrap();

        println!("Bus {:03} Device {:03} ID {:04x}:{:04x}",
            device.bus_number(),
            device.address(),
            device_desc.vendor_id(),
            device_desc.product_id());
    }
}

License

Distributed under the MIT License.

License note.

If you link native libusb (by example using vendored features) library statically then you must follow GNU LGPL from libusb.

rusb's People

Contributors

a1ien avatar adaszko avatar alufers avatar amyspark avatar anton-dutov avatar arkpar avatar avantgardnerio avatar dcuddeback avatar debris avatar detly avatar elmarco avatar elpiel avatar fpagliughi avatar jonas-schievink avatar kevinmehall avatar lespea avatar losynix avatar nelsonjchen avatar newam avatar nwsharp avatar otavio avatar oysteintveit-nordicsemi avatar pantsman0 avatar patrickelectric avatar r3n4ud avatar richardkiene avatar rossmacarthur avatar rukai avatar serpilliere avatar tecywiz121 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rusb's Issues

Vendored libusb does not work on older versions of macOS (below 10.12).

This is mainly related to the clock_gettime() function which is not available before macOS 10.12. On the other hand, libusb can be compiled on older systems (I tested 10.11) via Homebrew.

There is a define HAVE_CLOCK_GETTIME which is always set to 1 in the build.rs of the libusb1-sys, so it will fail on macOS 10.11 and below.

list which usb is android device

hey
how can just know how many android device is connected to usbs
not using them just list how many android devices there
thanks so much

Windows on ARM, Support.

ARM64 build for the windows on arm driver and rusb binary will definitely speed up the USB-based peripheral driver development and make ease of access to learn to build drives on a windows-based platform for hobbyists, students, and engineers to adopt rust as a safe programming language for driver development.

Not only WOR but it will also help to extend driver support for surface devices and other arm-based windows devices like Qualcomm dev kit, Nvidia Tegra kits, and Windows Surface ARM devices.

Please let us know when can we have an ARM64 version for Windows on ARM OS. We can help you test We have Windows on Rasberry Pi setup. We at Windows on Rasberry Pi community will be glad to extend support in testing your drivers and tools for ARM64 builds for drivers and tools.

libusb can return 0 from bulk reads.

In some of my testing using rusb, I ran into an issue where calls to io::Copy were ending earlier than expected.

I experimented, and apparently it is possible for a successful libusb_bulk_transfer to read 0 bytes.
Returning Ok(0) from bulk_read doesn't work well, because it suggests EOF. However, this is definitely not EOF.

What are your thoughts on returning rusb::Error::Interrupted in those cases as well?

Move bit-set behind a cfg flag

Would you be open to a pull request which converts bit-set to be an optional dependency based on a cfg option? If the cfg were disabled, DeviceHandle could just use the standard library's HashSet instead of BitSet

My reasoning is that for people who don't care about a speed or memory hit from using HashSet, there is no need to add the additional dependencies on bit-set and (transitively) bit-vec.

Device shouldn't reference context

We should follow libusb more closely there.

open() should be a method on the Context, not on Device. This would remove the need to track the context in Device.

Alternatively, make open() take a context argument.

One of the reason is that there are cases where all you have is a libusb_device*, without its context, and you may want to still wrap it with Device.

Add ability to select libusb commit for built-in libusb

Hi,

first of all: thanks for your work on rusb! I'm quite excited to use it in a project. Unfortunately, I ran into these issues in libusb 1.0.24. As of current master, they appear to have been fixed, but no new release has been made yet. I understand that it's not desirable to ship a non-release libusb in a release libusb1-sys, but maybe this problem could be tackled by adding a generic way to select any commit of libusb that should be built.

I am currently not clear on how such a selection mechanism should work. A commit could be passed as an environment variable but that's not very ergonomic. If we can figure out a clean design, I'd be happy to send a pull request. One potential option would be to rely on rust-lang/cargo#9175, which has landed earlier this year, is now available as part of Rust stable, and allows a user to set environment variables in a .cargo/config.toml. This file can be configured per project, so it should provide a satisfactory solution.

EDIT: I have an experimental implementation of this at Cu3PO42/rusb. The primary issue remaining with this is the lack of a proper version string for the commit. I'm not sure what it's used for so I don't know if just using the commit would be fine.

Compilation fails linking if there are different versions of libusb

This issue comes from an attempted install of probe-run-rp in which cargo would enter linking stage and then fail with various messages of undefined reference to libusb_xxxx. The weird part is that it only affected this crate, other crates using rusb compiled without even warnings.

This issue was solved after uninstalling the libraries libusb-dev and libusb-0.1-4 and leaving installed only the library libusb-1.0-0-dev.

I don't know if this is a bug or expected behaviour (after all, there were may versions of the same library).

Example app crashed on Windows

Tried running the list_devices example on Windows 7. It got the first four devices OK, then on the fifth one it crashed:

Bus 003 Device 005 ID 046d:c52f   12 Mbps
Device Descriptor:
  bcdUSB              2.00
  bDeviceClass        0x00
  bDeviceSubClass     0x00
  bDeviceProtocol     0x00
  bMaxPacketSize0        8
  idVendor          0x046d
  idProduct         0xc52f
  bcdDevice          30.00
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', C:\Temp\rusb\src\device_handle.rs:587:58
stack backtrace:
   0: std::sys_common::backtrace::_print::{{impl}}::fmt
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\sys_common\backtrace.rs:61
   1: core::fmt::write
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libcore\fmt\mod.rs:1025
   2: std::io::Write::write_fmt<std::sys::windows::stdio::Stderr>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\io\mod.rs:1427
   3: std::panicking::default_hook::{{closure}}
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\panicking.rs:193
   4: std::panicking::default_hook
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\panicking.rs:212
   5: std::panicking::rust_panic_with_hook
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\panicking.rs:471
   6: std::panicking::begin_panic_handler
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\panicking.rs:375
   7: core::panicking::panic_fmt
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libcore\panicking.rs:84
   8: core::panicking::panic_bounds_check
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libcore\panicking.rs:62
   9: rusb::device_handle::{{impl}}::read_string_descriptor::{{closure}}<rusb::context::GlobalContext>
             at .\src\device_handle.rs:587
  10: core::ops::function::impls::{{impl}}::call_once<(slice<u8>*),closure-0>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\libcore\ops\function.rs:285
  11: core::option::Option<slice<u8>*>::map<slice<u8>*,u16,mut closure-0*>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\libcore\option.rs:450
  12: core::iter::adapters::{{impl}}::next<u16,core::iter::adapters::Skip<core::slice::Chunks<u8>>,closure-0>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\libcore\iter\adapters\mod.rs:720
  13: alloc::vec::Vec<u16>::extend_desugared<u16,core::iter::adapters::Map<core::iter::adapters::Skip<core::slice::Chunks<u8>>, closure-0>>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\liballoc\vec.rs:2118
  14: alloc::vec::{{impl}}::spec_extend<u16,core::iter::adapters::Map<core::iter::adapters::Skip<core::slice::Chunks<u8>>, closure-0>>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\liballoc\vec.rs:2016
  15: alloc::vec::{{impl}}::from_iter<u16,core::iter::adapters::Map<core::iter::adapters::Skip<core::slice::Chunks<u8>>, closure-0>>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\liballoc\vec.rs:2010
  16: alloc::vec::{{impl}}::from_iter<u16,core::iter::adapters::Map<core::iter::adapters::Skip<core::slice::Chunks<u8>>, closure-0>>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\liballoc\vec.rs:1912
  17: core::iter::traits::iterator::Iterator::collect<core::iter::adapters::Map<core::iter::adapters::Skip<core::slice::Chunks<u8>>, closure-0>,alloc::vec::Vec<u16>>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\libcore\iter\traits\iterator.rs:1495
  18: rusb::device_handle::DeviceHandle<rusb::context::GlobalContext>::read_string_descriptor<rusb::context::GlobalContext>
             at .\src\device_handle.rs:590
  19: rusb::device_handle::DeviceHandle<rusb::context::GlobalContext>::read_manufacturer_string<rusb::context::GlobalContext>
             at .\src\device_handle.rs:611
  20: list_devices::print_device::{{closure}}<rusb::context::GlobalContext>
             at .\examples\list_devices.rs:108
  21: core::option::Option<mut list_devices::UsbDevice<rusb::context::GlobalContext>*>::map_or<mut list_devices::UsbDevice<rusb::context::GlobalContext>*,alloc::string::String,closure-0>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\libcore\option.rs:471
  22: list_devices::print_device<rusb::context::GlobalContext>
             at .\examples\list_devices.rs:105
  23: list_devices::list_devices
             at .\examples\list_devices.rs:54
  24: list_devices::main
             at .\examples\list_devices.rs:14
  25: std::rt::lang_start::{{closure}}<()>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\libstd\rt.rs:67
  26: std::panicking::try::do_call<closure-0,i32>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\panicking.rs:292
  27: panic_unwind::__rust_maybe_catch_panic
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libpanic_unwind\lib.rs:78
  28: std::rt::lang_start_internal
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libstd\rt.rs:51
  29: std::rt::lang_start<()>
             at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\src\libstd\rt.rs:67
  30: main
  31: __scrt_common_main_seh
             at d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
  32: BaseThreadInitThunk
  33: RtlUserThreadStart
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

For interest: A way to eleminate the need for the Context Trait

This code is quite involved. In essence, it uses a modified Arc that behaves differently on drop when dealing with a libusb default (null) context.

It makes extensive use of nightly Rust (allocator related APIs mostly) so won't be suitable for projects that are averse to that.

I thought you might find it useful for the future. It's not battle-tested - more of a suggested approach.

/// A libusb context.
#[derive(Debug)]
#[repr(transparent)]
pub struct Context(NonNull<ContextInner>);

unsafe impl Send for Context
{
}

unsafe impl Sync for Context
{
}

impl Drop for Context
{
	#[inline(always)]
	fn drop(&mut self)
	{
		let (previous_reference_count, wraps_default_libusb_context) = self.inner().decrement();
		let previous_reference_count = previous_reference_count.get();
		
		if wraps_default_libusb_context
		{
			if previous_reference_count == (ContextInner::MinimumReferenceCount + 1)
			{
				self.uninitialize();
			}
			else if previous_reference_count == ContextInner::MinimumReferenceCount
			{
				self.free();
			}
		}
		else
		{
			if unlikely!(previous_reference_count == ContextInner::MinimumReferenceCount)
			{
				self.uninitialize();
				self.free();
			}
		}
	}
}

impl Clone for Context
{
	#[inline(always)]
	fn clone(&self) -> Self
	{
		self.fallible_clone().expect("Could not reinitialize (but this should not be possible as the default context always reinitializes on first use once the reference count has fallen to 1)")
	}
}

impl Context
{
	/// The default context.
	#[inline(always)]
	pub fn default() -> Result<Self, ContextInitializationError>
	{
		static Cell: SyncOnceCell<Context> = SyncOnceCell::new();
		let reference = Cell.get_or_try_init(|| Self::wrap_libusb_context(null_mut()))?;
		reference.fallible_clone()
	}
	
	/// A specialized context.
	#[inline(always)]
	pub fn new() -> Result<Self, ContextInitializationError>
	{
		let mut libusb_context = MaybeUninit::uninit();
		ContextInner::initialize(libusb_context.as_mut_ptr())?;
		let libusb_context = unsafe { libusb_context.assume_init() };
		match Self::wrap_libusb_context(libusb_context)
		{
			Ok(this) => Ok(this),
			
			Err(error) =>
			{
				unsafe { libusb_exit(libusb_context) }
				Err(ContextInitializationError::CouldNotAllocateMemoryInRust(error))
			}
		}
	}
	
	#[inline(always)]
	pub(crate) fn as_ptr(&self) -> *mut libusb_context
	{
		self.inner().libusb_context
	}
	
	#[inline(always)]
	fn fallible_clone(&self) -> Result<Self, ContextInitializationError>
	{
		let (previous_reference_count, wraps_default_libusb_context) = self.inner().increment();
		if wraps_default_libusb_context
		{
			if unlikely!(previous_reference_count == ContextInner::MinimumReferenceCount)
			{
				ContextInner::reinitialize()?
			}
		}
		
		Ok(Self(self.0))
	}
	
	/// `libusb_context` will NOT have been initialized if it is the default (null) context.
	/// `libusb_context` will have been initialized if it is the default (null) context.
	fn wrap_libusb_context(libusb_context: *mut libusb_context) -> Result<Self, AllocError>
	{
		let slice = Global.allocate(Self::layout())?;
		let inner: NonNull<ContextInner> = slice.as_non_null_ptr().cast();
		
		unsafe
		{
			inner.as_ptr().write
			(
				ContextInner
				{
					libusb_context,
					
					reference_count: AtomicUsize::new(ContextInner::MinimumReferenceCount)
				}
			)
		};
		
		Ok(Self(inner))
	}
	
	#[inline(always)]
	fn uninitialize(&self)
	{
		self.inner().uninitialize();
	}
	
	#[inline(always)]
	fn free(&self)
	{
		unsafe { Global.deallocate(self.0.cast(), Self::layout()) }
	}
	
	#[inline(always)]
	fn inner<'a>(&self) -> &'a ContextInner
	{
		unsafe { & * (self.0.as_ptr()) }
	}
	
	#[inline(always)]
	const fn layout() -> Layout
	{
		Layout::new::<ContextInner>()
	}
}

/// Designed so that when the default libusb context is held in a static variable, such as a SyncLazyCell, libusb_exit() is still called even though a static reference is held.
///
/// Also designed so that if a static reference is then later used after being the only held reference, the libusb default context is re-initialized.
#[derive(Debug)]
struct ContextInner
{
	libusb_context: *mut libusb_context,
	
	reference_count: AtomicUsize,
}

impl ContextInner
{
	const MinimumReferenceCount: usize = 1;
	
	const NoReferenceCount: usize = Self::MinimumReferenceCount - 1;
	
	const ReferenceChange: usize = 1;
	
	#[inline(always)]
	fn decrement(&self) -> (NonZeroUsize, bool)
	{
		debug_assert_ne!(self.current_reference_count(), Self::NoReferenceCount);
		
		let previous_reference_count = self.reference_count.fetch_sub(Self::ReferenceChange, SeqCst);
		(new_non_zero_usize(previous_reference_count), self.is_default_libusb_context())
	}
	
	#[inline(always)]
	fn increment(&self) -> (usize, bool)
	{
		debug_assert_ne!(self.current_reference_count(), Self::NoReferenceCount);
		
		let previous_reference_count = self.reference_count.fetch_add(Self::ReferenceChange, SeqCst);
		(previous_reference_count, self.is_default_libusb_context())
	}
	
	#[inline(always)]
	fn reinitialize() -> Result<(), ContextInitializationError>
	{
		Self::initialize(null_mut())
	}
	
	#[inline(always)]
	fn uninitialize(&self)
	{
		debug_assert_eq!(self.current_reference_count(), Self::NoReferenceCount);
		
		unsafe { libusb_exit(self.libusb_context) }
	}
	
	#[inline(always)]
	fn initialize(libusb_context_pointer: *mut *mut libusb_context) -> Result<(), ContextInitializationError>
	{
		use ContextInitializationError::*;
		
		let result = unsafe { libusb_init(libusb_context_pointer) };
		if likely!(result == 0)
		{
			Ok(())
		}
		else if likely!(result < 0)
		{
			let error = match result
			{
				LIBUSB_ERROR_IO => InputOutputError,
				
				LIBUSB_ERROR_INVALID_PARAM => unreachable!("Windows and Linux have a 4096 byte transfer limit (including setup byte)"),
				
				LIBUSB_ERROR_ACCESS => AccessDenied,
				
				LIBUSB_ERROR_NO_DEVICE => NoDevice,
				
				LIBUSB_ERROR_NOT_FOUND => RequestedResourceNotFound,
				
				LIBUSB_ERROR_BUSY => unreachable!("Should not have been called from an event handling context"),
				
				LIBUSB_ERROR_TIMEOUT => TimedOut,
				
				LIBUSB_ERROR_OVERFLOW => BufferOverflow,
				
				LIBUSB_ERROR_PIPE => Pipe,
				
				LIBUSB_ERROR_INTERRUPTED => unreachable!("Does not invoke handle_events()"),
				
				LIBUSB_ERROR_NO_MEM => OutOfMemoryInLibusb,
				
				LIBUSB_ERROR_NOT_SUPPORTED => NotSupported,
				
				-98 ..= -13 => panic!("Newly defined error code {}", result),
				
				LIBUSB_ERROR_OTHER => Other,
				
				_ => unreachable!("LIBUSB_ERROR out of range: {}", result)
			};
			Err(error)
		}
		else
		{
			unreachable!("Positive result {} from libusb_init()")
		}
	}
	
	#[inline(always)]
	const fn is_default_libusb_context(&self) -> bool
	{
		self.libusb_context.is_null()
	}
	
	#[inline(always)]
	fn current_reference_count(&self) -> usize
	{
		self.reference_count.load(SeqCst)
	}
}

/// A context initialization error.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum ContextInitializationError
{
	CouldNotAllocateMemoryInRust(AllocError),
	
	InputOutputError,
	
	AccessDenied,
	
	NoDevice,
	
	RequestedResourceNotFound,
	
	TimedOut,
	
	BufferOverflow,
	
	Pipe,
	
	OutOfMemoryInLibusb,
	
	NotSupported,
	
	Other,
}

impl Display for ContextInitializationError
{
	#[inline(always)]
	fn fmt(&self, f: &mut Formatter) -> fmt::Result
	{
		Debug::fmt(self, f)
	}
}

impl error::Error for ContextInitializationError
{
	#[inline(always)]
	fn source(&self) -> Option<&(dyn error::Error + 'static)>
	{
		use ContextInitializationError::*;
		
		match self
		{
			CouldNotAllocateMemoryInRust(cause) => Some(cause),
			
			_ => None,
		}
	}
}

impl From<AllocError> for ContextInitializationError
{
	#[inline(always)]
	fn from(cause: AllocError) -> Self
	{
		ContextInitializationError::CouldNotAllocateMemoryInRust(cause)
	}
}

Plans/roadmap

Hi, it's nice to see someone having a go at a Rust-y version of the new libusb API :)

Do you have particular goals or features that you want to add? I wouldn't mind helping with the asynchronous API if that's something you plan to do (although I've never done something like that before, so I don't know how much I can contribute).

Undefined behavior if hotplug callback panics

I thought I'd review the hotplug code after seeing #50, and found another issue there. hotplug_callback is extern "system" (called by libusb), and calls into a method on a user-provided safe method of the Hotplug trait. If that method panics, it would unwind through FFI, which is undefined behavior. It should catch_unwind, and either abort the process, ignore the panic, or try to send it back to a Rust stack frame to resume_unwind somehow.

Additionally, hotplug_callback uses Box::from_raw to take ownership of the user_data and then mem::forgets it at the end. This means that panic unwinding would cause that Box to be freed. Instead of Box::from_raw, it should cast the raw pointer to &mut to avoid taking ownership of it.

Implementation help

Sorry I don't know English, I'm using the translator.

I'm migrating a Python project to Rust and I'm looking for a replacement for the "python-usbtmc" library and found this one.

My need is to write and read data from a Tektronix oscilloscope, writing I was able to do it but I was unable to read the data.

Any tips on how to do it?

Use ArrayVec rather than Vec for Device ports()

The current code allocates from the heap after already allocating from the stack a suitably sized array. This is inefficient but also unnecessary. Allocating Vecs in library code, as a general principal, should be avoided if at all possible, and, if absolutely necessary, it would be nice to be able to capture failed allocation. It's a long standing source of problems in long-running processes.

pub use libusb1-sys?

More of a question than an issue: I'm using rusb for most USB access, and libusb1-sys to access the libusb asynchronous interface. Since rusb depends on libusb1-sys, I wonder if it might be better if rusb exposed the libusb1-sys that it uses, rather than requiring projects that use both to depend on both libraries separately?

In particular, I wonder if there's some risk of the two libraries using subtly different versions of libusb and causing errors.

Cannot create Device from file descriptor on Android

Hello, and thank you for your fine library. Unfortunately, I'm trying to use it on Android, which is... less fine. When I run as root I can open USB Devices, but when I am running as a regular user / APK the only way to open them seems to be through the Android Java API. That API on the other-hand does expose the underlying file descriptor, and lib usb does provide libusb_wrap_sys_device.

So, I was wondering if you would be open to a PR that either constructed a rusb device from a FD or from a raw libusb_device? I certainly understand there are arguments to be had on both sides. I'm just trying to figure out which way I should go with my project.

Thanks!

Improve the precision of error handling internally

I was looking at the internals of Device.get_ports(), and it swallows all the error codes returned by libusb, and even those that can never be.

This means that an downstream user can enter a state of unknown validity or impossible validity after checking the Result<_, Error> returned; not doing so violates the fail fast maxim.

Instead, code calling a third party C library must explicitly check for the documented errors it can return. In this example, it should do something like:-

	let mut port_numbers  = ArrayVec::new_const();
	let result = unsafe { libusb_get_port_numbers(device.as_raw(), port_numbers.as_mut_ptr(), MaximumDevicePortNumbers as _) };
	if likely!(result >= 0)
	{
		let count = result as usize;
		unsafe { port_numbers.set_len(count) };
	}
	else if likely!(result == LIBUSB_ERROR_OVERFLOW)
	{
		panic!("USB violates specification with more than 7 ports")
	}
	else
	{
		unreachable!("Undocumented error code from libusb_get_port_numbers(), {}", result)
	}

(likely! is a macro I use). Obviously, coding this for every call is tedious and time-consuming, but that's the cost of wrapping third party C libraries with their naive error handling strategies and use of a shared pool of constants.

Likewise this implies that rusb should (indeed, must) have more than one kind of Error.

Instead of Option<&[u8]> for the various `extra` fields, just return a &[u8]

I've written lots of code to do length checks today on the various extra fields, and its unnecessarily painful. What you might want to do instead is use Option just in case libusb returns a null - it seems unlikely, as the pointer it's using is self-referential, but I wouldn't trust a third party C library to do the right thing...

Unsound Send/Sync bound for Device and DeviceHandle

Hello fellow Rustacean,
we (Rust group @sslab-gatech) found a memory-safety/soundness issue in this crate while scanning Rust code on crates.io for potential vulnerabilities.

Issue Description

rusb/src/device.rs

Lines 34 to 35 in 12ee91d

unsafe impl<T: UsbContext> Send for Device<T> {}
unsafe impl<T: UsbContext> Sync for Device<T> {}

rusb/src/device_handle.rs

Lines 127 to 128 in 12ee91d

unsafe impl<T: UsbContext> Send for DeviceHandle<T> {}
unsafe impl<T: UsbContext> Sync for DeviceHandle<T> {}

Device and DeviceHandle implement Send and Sync trait for all T types that implement UsbContext. UsbContext trait has neither Send nor Sync bound and can be implemented from the user side. This permits writing a custom non-thread safe UsbContext implementation in safe Rust code, which can cause a data race when used with Device or DeviceHandle.

If UsbContext is not expected to be implemented by users, making UsbContext a sealed trait can solve this problem. Otherwise, a proper bound should be added to Send/Sync implementations of Device and DeviceHandle (<T: Send/Sync + UsbContext>) or to the definition of UsbContext (UsbContext: Send + Sync).

Unused "Success" Error variant

Hey, first of all, thanks for this library!

In the Error enum the first variant is Success, which I believe should never be constructed as in all functions I've checked, you consider 0 as Ok(res).

I believe It would also be unidiomatic to return an Error::Success at any given time.

Would you consider removing that variant? It would be a breaking change, so we'd need to wait for 0.6 to include it.

I can create a Pull Request if you agree.

segmentation fault

segmentation fault in Alpine Linux
code:

use libusb1_sys as ffi;

pub fn main() {
    let version = unsafe { &*ffi::libusb_get_version() };

    println!(
        "libusb v{}.{}.{}.{}",
        version.major, version.minor, version.micro, version.nano
    );
}

build log:

$ cargo run -vv
   Compiling cc v1.0.69
     Running `CARGO=/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/bin/cargo CARGO_CRATE_NAME=cc CARGO_MANIFEST_DIR=/home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.69 CARGO_PKG_AUTHORS='Alex Crichton <[email protected]>' CARGO_PKG_DESCRIPTION='A build-time dependency for Cargo build scripts to assist in invoking the native
C compiler to compile native C code into a static archive to be linked into Rust
code.
' CARGO_PKG_HOMEPAGE='https://github.com/alexcrichton/cc-rs' CARGO_PKG_LICENSE=MIT/Apache-2.0 CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=cc CARGO_PKG_REPOSITORY='https://github.com/alexcrichton/cc-rs' CARGO_PKG_VERSION=1.0.69 CARGO_PKG_VERSION_MAJOR=1 CARGO_PKG_VERSION_MINOR=0 CARGO_PKG_VERSION_PATCH=69 CARGO_PKG_VERSION_PRE='' LD_LIBRARY_PATH='/home/niksun/development/rust/usb/target/debug/deps:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib' rustc --crate-name cc --edition=2018 /home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.69/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 -C metadata=bc48367fbd4943a1 -C extra-filename=-bc48367fbd4943a1 --out-dir /home/niksun/development/rust/usb/target/debug/deps -L dependency=/home/niksun/development/rust/usb/target/debug/deps --cap-lints warn`
   Compiling libc v0.2.98
   Compiling pkg-config v0.3.19
     Running `CARGO=/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/bin/cargo CARGO_CRATE_NAME=build_script_build CARGO_MANIFEST_DIR=/home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98 CARGO_PKG_AUTHORS='The Rust Project Developers' CARGO_PKG_DESCRIPTION='Raw FFI bindings to platform libraries like libc.
' CARGO_PKG_HOMEPAGE='https://github.com/rust-lang/libc' CARGO_PKG_LICENSE='MIT OR Apache-2.0' CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=libc CARGO_PKG_REPOSITORY='https://github.com/rust-lang/libc' CARGO_PKG_VERSION=0.2.98 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=2 CARGO_PKG_VERSION_PATCH=98 CARGO_PKG_VERSION_PRE='' LD_LIBRARY_PATH='/home/niksun/development/rust/usb/target/debug/deps:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib' rustc --crate-name build_script_build /home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98/build.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 --cfg 'feature="default"' --cfg 'feature="std"' -C metadata=5a8c16f1ed0a03b2 -C extra-filename=-5a8c16f1ed0a03b2 --out-dir /home/niksun/development/rust/usb/target/debug/build/libc-5a8c16f1ed0a03b2 -L dependency=/home/niksun/development/rust/usb/target/debug/deps --cap-lints warn`
     Running `CARGO=/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/bin/cargo CARGO_CRATE_NAME=pkg_config CARGO_MANIFEST_DIR=/home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/pkg-config-0.3.19 CARGO_PKG_AUTHORS='Alex Crichton <[email protected]>' CARGO_PKG_DESCRIPTION='A library to run the pkg-config system tool at build time in order to be used in
Cargo build scripts.
' CARGO_PKG_HOMEPAGE='' CARGO_PKG_LICENSE=MIT/Apache-2.0 CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=pkg-config CARGO_PKG_REPOSITORY='https://github.com/rust-lang/pkg-config-rs' CARGO_PKG_VERSION=0.3.19 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=3 CARGO_PKG_VERSION_PATCH=19 CARGO_PKG_VERSION_PRE='' LD_LIBRARY_PATH='/home/niksun/development/rust/usb/target/debug/deps:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib' rustc --crate-name pkg_config /home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/pkg-config-0.3.19/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 -C metadata=12760bb814148db2 -C extra-filename=-12760bb814148db2 --out-dir /home/niksun/development/rust/usb/target/debug/deps -L dependency=/home/niksun/development/rust/usb/target/debug/deps --cap-lints warn`
     Running `/home/niksun/development/rust/usb/target/debug/build/libc-5a8c16f1ed0a03b2/build-script-build`
[libc 0.2.98] cargo:rerun-if-changed=build.rs
[libc 0.2.98] cargo:rustc-cfg=freebsd11
[libc 0.2.98] cargo:rustc-cfg=libc_priv_mod_use
[libc 0.2.98] cargo:rustc-cfg=libc_union
[libc 0.2.98] cargo:rustc-cfg=libc_const_size_of
[libc 0.2.98] cargo:rustc-cfg=libc_align
[libc 0.2.98] cargo:rustc-cfg=libc_core_cvoid
[libc 0.2.98] cargo:rustc-cfg=libc_packedN
[libc 0.2.98] cargo:rustc-cfg=libc_cfg_target_vendor
     Running `CARGO=/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/bin/cargo CARGO_CRATE_NAME=libc CARGO_MANIFEST_DIR=/home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98 CARGO_PKG_AUTHORS='The Rust Project Developers' CARGO_PKG_DESCRIPTION='Raw FFI bindings to platform libraries like libc.
' CARGO_PKG_HOMEPAGE='https://github.com/rust-lang/libc' CARGO_PKG_LICENSE='MIT OR Apache-2.0' CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=libc CARGO_PKG_REPOSITORY='https://github.com/rust-lang/libc' CARGO_PKG_VERSION=0.2.98 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=2 CARGO_PKG_VERSION_PATCH=98 CARGO_PKG_VERSION_PRE='' LD_LIBRARY_PATH='/home/niksun/development/rust/usb/target/debug/deps:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib' OUT_DIR=/home/niksun/development/rust/usb/target/debug/build/libc-25c8dc8919ea7310/out rustc --crate-name libc /home/niksun/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 --cfg 'feature="default"' --cfg 'feature="std"' -C metadata=49e285bc42127cb4 -C extra-filename=-49e285bc42127cb4 --out-dir /home/niksun/development/rust/usb/target/debug/deps -L dependency=/home/niksun/development/rust/usb/target/debug/deps --cap-lints warn --cfg freebsd11 --cfg libc_priv_mod_use --cfg libc_union --cfg libc_const_size_of --cfg libc_align --cfg libc_core_cvoid --cfg libc_packedN --cfg libc_cfg_target_vendor`
   Compiling libusb1-sys v0.5.0 (/home/niksun/development/rust/rusb/libusb1-sys)
     Running `CARGO=/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/bin/cargo CARGO_CRATE_NAME=build_script_build CARGO_MANIFEST_DIR=/home/niksun/development/rust/rusb/libusb1-sys CARGO_PKG_AUTHORS='David Cuddeback <[email protected]>:Ilya Averyanov <[email protected]>' CARGO_PKG_DESCRIPTION='FFI bindings for libusb.' CARGO_PKG_HOMEPAGE='https://github.com/a1ien/rusb' CARGO_PKG_LICENSE=MIT CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=libusb1-sys CARGO_PKG_REPOSITORY='https://github.com/a1ien/rusb.git' CARGO_PKG_VERSION=0.5.0 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=5 CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' LD_LIBRARY_PATH='/home/niksun/development/rust/usb/target/debug/deps:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib' rustc --crate-name build_script_build --edition=2018 /home/niksun/development/rust/rusb/libusb1-sys/build.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 -C metadata=110a414e8f76d54a -C extra-filename=-110a414e8f76d54a --out-dir /home/niksun/development/rust/usb/target/debug/build/libusb1-sys-110a414e8f76d54a -C incremental=/home/niksun/development/rust/usb/target/debug/incremental -L dependency=/home/niksun/development/rust/usb/target/debug/deps --extern cc=/home/niksun/development/rust/usb/target/debug/deps/libcc-bc48367fbd4943a1.rlib --extern pkg_config=/home/niksun/development/rust/usb/target/debug/deps/libpkg_config-12760bb814148db2.rlib`
     Running `/home/niksun/development/rust/usb/target/debug/build/libusb1-sys-110a414e8f76d54a/build-script-build`
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=LIBUSB_1.0_NO_PKG_CONFIG
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64-unknown-linux-musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64_unknown_linux_musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=HOST_PKG_CONFIG_PATH
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_PATH
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64-unknown-linux-musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64_unknown_linux_musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=HOST_PKG_CONFIG_LIBDIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64-unknown-linux-musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64_unknown_linux_musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=HOST_PKG_CONFIG_SYSROOT_DIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=SYSROOT
[libusb1-sys 0.5.0] cargo:rustc-link-search=native=/usr/lib
[libusb1-sys 0.5.0] cargo:rustc-link-lib=usb-1.0
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64-unknown-linux-musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64_unknown_linux_musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=HOST_PKG_CONFIG_PATH
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_PATH
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64-unknown-linux-musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64_unknown_linux_musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=HOST_PKG_CONFIG_LIBDIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64-unknown-linux-musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64_unknown_linux_musl
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=HOST_PKG_CONFIG_SYSROOT_DIR
[libusb1-sys 0.5.0] cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
[libusb1-sys 0.5.0] cargo:include=/usr/include/libusb-1.0
[libusb1-sys 0.5.0] cargo:version_number=1.0.24
     Running `CARGO=/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/bin/cargo CARGO_CRATE_NAME=libusb1_sys CARGO_MANIFEST_DIR=/home/niksun/development/rust/rusb/libusb1-sys CARGO_PKG_AUTHORS='David Cuddeback <[email protected]>:Ilya Averyanov <[email protected]>' CARGO_PKG_DESCRIPTION='FFI bindings for libusb.' CARGO_PKG_HOMEPAGE='https://github.com/a1ien/rusb' CARGO_PKG_LICENSE=MIT CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=libusb1-sys CARGO_PKG_REPOSITORY='https://github.com/a1ien/rusb.git' CARGO_PKG_VERSION=0.5.0 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=5 CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' LD_LIBRARY_PATH='/home/niksun/development/rust/usb/target/debug/deps:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib' OUT_DIR=/home/niksun/development/rust/usb/target/debug/build/libusb1-sys-4d7d9807ec56a82d/out rustc --crate-name libusb1_sys --edition=2018 /home/niksun/development/rust/rusb/libusb1-sys/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 -C metadata=146a826da8a6d5f3 -C extra-filename=-146a826da8a6d5f3 --out-dir /home/niksun/development/rust/usb/target/debug/deps -C incremental=/home/niksun/development/rust/usb/target/debug/incremental -L dependency=/home/niksun/development/rust/usb/target/debug/deps --extern libc=/home/niksun/development/rust/usb/target/debug/deps/liblibc-49e285bc42127cb4.rmeta -L native=/usr/lib -l usb-1.0`
   Compiling usb v0.1.0 (/home/niksun/development/rust/usb)
     Running `CARGO=/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/bin/cargo CARGO_BIN_NAME=usb CARGO_CRATE_NAME=usb CARGO_MANIFEST_DIR=/home/niksun/development/rust/usb CARGO_PKG_AUTHORS='' CARGO_PKG_DESCRIPTION='' CARGO_PKG_HOMEPAGE='' CARGO_PKG_LICENSE='' CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=usb CARGO_PKG_REPOSITORY='' CARGO_PKG_VERSION=0.1.0 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=1 CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' CARGO_PRIMARY_PACKAGE=1 LD_LIBRARY_PATH='/home/niksun/development/rust/usb/target/debug/deps:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib:/home/niksun/.rustup/toolchains/stable-x86_64-unknown-linux-musl/lib' rustc --crate-name usb --edition=2018 src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 -C metadata=94c3ab235d114386 -C extra-filename=-94c3ab235d114386 --out-dir /home/niksun/development/rust/usb/target/debug/deps -C incremental=/home/niksun/development/rust/usb/target/debug/incremental -L dependency=/home/niksun/development/rust/usb/target/debug/deps --extern libusb1_sys=/home/niksun/development/rust/usb/target/debug/deps/liblibusb1_sys-146a826da8a6d5f3.rlib -L native=/usr/lib`
    Finished dev [unoptimized + debuginfo] target(s) in 7.95s
     Running `target/debug/usb`
[1]    24324 segmentation fault  cargo run -vv

ldd:

ldd target/debug/usb
        /lib/ld-musl-x86_64.so.1 (0x7fc934ac4000)
        libusb-1.0.so.0 => /usr/lib/libusb-1.0.so.0 (0x7fc934a42000)
        libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7fc934ac4000)

Unable to read USB device data on Windows

fn print_device<T: UsbContext>(device_desc: &DeviceDescriptor, handle: &mut Option<UsbDevice<T>>)
{
    println!(
        "  iManufacturer        {:?}",
            handle.as_mut().map_or(String::new(), |h| h
            .handle
            .read_manufacturer_string(h.language, device_desc, h.timeout)
            .unwrap())
    );
    println!(
        "  iproduct        {:?}",
            handle.as_mut().map_or(String::new(), |h| h
            .handle
            .read_product_string_ascii(device_desc)
            .unwrap())
    );
}

image

Hello! I wrote this test function based on the example I found in the rusb repo. I have encountered this issue on Windows 10 1909. The manufacturer and product information do not seem to be accessible on Windows. I have tried running the application as a standard user and as administrator and received the same results as shown in the console output.

I have tested the same code in a Linux environment (Ubuntu 20.04 and Raspberry Pi OS) and properly printed the desired information.

I tried using both .read_product_string() and .read_product_string_ascii() and achieved the same results (an empty string).

Thank you for your time and insight!

Add Windows CI

Adding windows CI to this project would not only be useful for ensuring it continues to work, but would also act as a demonstration of how users of this library can setup their own CI.

Need safe cloning of device handles for async support?

This is a question, not a definite problem.

When using async in C I think it's typical to open a device, then copy the device handle into multiple libusb_fill_bulk_transfer(). This is safe if:

  • You let all transfers finish before closing the device handle.
  • You only close the device handle once.

Of course in C you have to keep track of this the hard way.

If we want safe async in Rust, I think we first need make DeviceHandle safe to copy. And somehow prevent closing a device if any handles still exist.

Thoughts?

Document timeout unit, add non_blocking read/write interrupts

I'd been using micros for the timeout of an interrupt read and this was causing some very very janky behaviour that I was struggling to debug. The docs don't mention the unit required for timeouts and it seems 1 millisecond is the lowest I can go.

It would also be nice to have a non-blocking read/write_interrupt call.

Incorrect `u8::MAX `?

[  164s]    Compiling rusb v0.6.2
[  164s] error[E0599]: no associated item named `MAX` found for type `u8` in the current scope
[  164s]   --> /home/abuild/rpmbuild/BUILD/asus-nb-ctrl-1.0.3/vendor/rusb/src/device_handle.rs:89:33
[  164s]    |
[  164s] 89 |         while self.index <= u8::MAX as u16 {
[  164s]    |                                 ^^^ associated item not found in `u8`
[  164s]    |
[  164s] help: you are looking for the module in `std`, not the primitive type
[  164s]    |
[  164s] 89 |         while self.index <= std::u8::MAX as u16 {
[  164s]    |                             ^^^^^^^^^^^^
[  164s] 
[  164s] error: aborting due to previous error

Unsure what is going on here. The last version I was using is 0.6.0, and I'm building for four distros in Open Build. I just updated to 6.0.2 and got the above error (and git blame indicates the related changes are 2 months old).

The distros that fail are fedora and ubuntu, while arch and opensuse are fine.

  • Arch rust-1:1.46.0-1
  • fedora rust-1.42.0-1.fc32
  • opensuse rust-1.44.1-3.1
  • ubuntu rust-1.41-1

Bit of a pain in the bum. The change is breaking, but only because of compiler changes. I've no idea how you would want to approach this..

New release

Please make a new release so it can be used on win32.

Async Idea

I've got an idea on how to make the async API work. I've got it working on a local branch and it seems to be working pretty well. It basically revolves around a struct that looks like this:

pub struct AsyncTransfer {
	pub ptr: *mut libusb_transfer,
	pub buff: Vec<u8>,
	pub recv: Pin<Box<VecDeque<TransferStatus>>>,
}

The buff vector is the vector where libusb puts the data. ptr is the transfer struct provided by libusb. recv is where we accumulate the results. TransferStatus looks like this:

pub enum TransferStatus {
	Completed(Vec<u8>),
	Error,
	TimedOut,
	Stall,
	NoDevice,
	Overflow,
	Unknown(i32)
}

The callback function is a private function inside impl AsynTransfer and looks like this:

	extern "system" fn callback(xfer_ptr: *mut libusb_transfer) {

		unsafe {
			let xfer = match (*xfer_ptr).status {
				0 => {
					// Clone the memory into a vector
					let slice:&[u8] = std::slice::from_raw_parts((*xfer_ptr).buffer, (*xfer_ptr).actual_length as usize);
					TransferStatus::Completed(slice.to_vec())
				},
				1 => TransferStatus::Error,
				2 => TransferStatus::TimedOut,
				3 => TransferStatus::Stall,
				4 => TransferStatus::NoDevice,
				5 => TransferStatus::Overflow,
				n => TransferStatus::Unknown(n),
			};

			// Update the parser stored in the user data field
			let xfer_deque:*mut VecDeque<TransferStatus> = (*xfer_ptr).user_data as *mut VecDeque<TransferStatus>;
			(*xfer_deque).push_back(xfer);

			// Resubmit the transfer
			assert!(libusb1_sys::libusb_submit_transfer(xfer_ptr) == 0);
		}

	}

Basically, it puts the result into a VecDeque and the scope that owns the struct can process these whenever it wants. This is not threadsafe, but doesn't pretend to be. If you try to make this struct Send or Sync, the compiler gives an error. The callback function runs in the same thread that created the struct when you call libusb1_sys::libusb_handle_events.

Like I said, I've been testing it and it seems to work pretty well. If you're interested, I'll push the branch and create a pull request if you give me the permissions I need.

Unsoundness with !Send and Hotplug

If the Hotplug passed to register_callback is !Send, but UsbContext::handle_events is called from another thread, the Hotplug is sent to that thread.

The following code demonstrates what I believe is the unsound behaviour:

use rusb::{Device, Error, GlobalContext, Hotplug, UsbContext};

use std::rc::Rc;

#[derive(Default)]
struct Unsend(Rc<()>);

impl Unsend {
    fn log(&self) {
        eprintln!(
            "ptr: 0x{:x}, thread: {:?}",
            Rc::as_ptr(&self.0) as usize,
            std::thread::current()
        );
    }
}

impl<T> Hotplug<T> for Unsend
where
    T: UsbContext,
{
    fn device_arrived(&mut self, _: Device<T>) {
        self.log()
    }

    fn device_left(&mut self, _: Device<T>) {
        self.log()
    }
}

fn main() -> Result<(), Error> {
    let foo = Unsend::default();
    foo.log();

    /*
    // Fails to compile because Unsend is not Send.
    std::thread::spawn(move || {
        drop(foo);
    });
    */

    let context = GlobalContext::default();
    let reg = context
        .register_callback(None, None, None, Box::new(foo))
        .unwrap();

    let handler = std::thread::spawn(move || loop {
        context.handle_events(None).ok();
    });

    handler.join().unwrap();
    context.unregister_callback(reg);
    Ok(())
}

When (un)plugging a device, it prints (note the different thread ids):

ptr: 0x55de6d819bb0, thread: Thread { id: ThreadId(1), name: Some("main") }
ptr: 0x55de6d819bb0, thread: Thread { id: ThreadId(2), name: None }
ptr: 0x55de6d819bb0, thread: Thread { id: ThreadId(2), name: None }

write_bulk and write_interrupt speed

It seems no matter what I do here, writing takes between 500u to 1ms (64bytes). Which is far far too slow for what I need. The same data written by whatever means I've captured in Windows with wireshark is sub-nanos.

Context: I'm reverse engineering a usb protocol to control my keyboard leds. I'm using rusb to write packets, and the write is too slow.

I'm unsure if this is a libusb issue, rusb, or other. Device speed is reported and "Full".

Adding some #[inline] to some parts of rusb and my own code helps by 100-200u sort of.

Add additional automatic derives for SyncType and UsageType

These types lack some ordering derives. Whilst it might seem odd to some, having a comparison implementation on these types means they can have a consist sort order when being displayed. It might also be nice to implement the Display trait, perhaps using strum.

Support Serde

I've just started using rusb for a project involving a replacement for PC/SC, and I've found it very good - much better than many c-library wrappers.

One thing that is missing is optional support for serde for some of the provided types, eg Version. I'm currently dumping out captured properties to debug an issue, and it'd be nice to have serde support for these, rather than having to write wrappers or the like.

Should be trivial to add. Please?

`EndpointDescriptor` missing `bRefresh` and `bSynchAddress` fields

Hi! Is there a way around the lack of bRefresh and bSynchAddress for the EndpointDescriptor?

I know libusb1-sys does provide all the fields but this crate doesn't provide an API to access those fields or even to know the length of the EndpointDescriptor, since it can be either 7 or 9 bytes long.

Any help would be appreciated. Thanks!

[Qustion] Resources to use this crate

Hi there, this might not be the right place to ask. I'm not new to rust but I have never used libusb or done any USB programming in general. I have read up some some info about this, but I'm still struggling to understand how to use this lib properly. Are there any resources someone can point to me?
My goal is to replicate some of the app functionality in Linux (windows only device) by wire sharking the packates. So far I can see what I'd like to read from the device but don't know how to do it.
any help would be greatly appreciated
Thanks

Support libusb async api

Libusb provides an asynchronous api - for example, libusb_fill_bulk_transfer(), libusb_submit_transfer(), etc. Asynchronous API is important for getting good performance/latency from USB - in my case, my bulk transfers have a latency of 40-120ms, but if I were to be using the asynchronous API (which allows you to queue up multiple reads), my transfers would take only 4ms (!!!).

I know that some work on this is present in #48, and mentioned in #47. I wanted to open this issue as more of a feature tracker / feature request.

Note that integrating this with Rust's async functions is not necessarily necessary - it would be sufficient to stay consistent with libusb's approach where you have to repeatedly call libusb_handle_events() in a loop to drive the asynchronous callbacks in the same thread.

Device busy

When I try to get the USB information, like read_serial_number_string_ascii in the Hotplug event, it prompts Device busy.

use std::thread;

use rusb::{self, Hotplug, UsbContext};

struct HotplugHandler;

impl<T: UsbContext> Hotplug<T> for HotplugHandler {
    fn device_left(&mut self, device: rusb::Device<T>) {
        println!("device left: {:?}", device);
    }
    fn device_arrived(&mut self, device: rusb::Device<T>) {
        let desc = device.device_descriptor().unwrap();
        let handle = device.open().unwrap();

        let serial_num = handle.read_serial_number_string_ascii(&desc).unwrap();
        println!("device arrived: {:?}, serial_num: {}", device, serial_num);
    }
}

fn main() {
    if rusb::has_hotplug() {
        let ctx = rusb::GlobalContext::default();

        let mut reg = Some(
            ctx.register_callback(None, None, None, Box::new(HotplugHandler {}))
                .unwrap(),
        );

        let handle = thread::spawn(move || loop {
            ctx.handle_events(None).unwrap();
        });

        handle.join().unwrap();

        if let Some(reg) = reg.take() {
            ctx.unregister_callback(reg);
        }
    } else {
        eprintln!("libusb hotplug api unsupported");
    }
}

error log

device left: Bus 001 Device 070: ID 2717:ff48
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Busy', src/main.rs:15:72
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Change TransferType

TransferType should be defined as:-

#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum UsbTransferType
{
	/// Control endpoint.
	Control,
	
	/// Isochronous endpoint.
	Isochronous
	{
		sync_type: UsbIschronousTransferSynchronizationType,
		
		usage_type: UsbIschronousTransferUsageType,
	},
	
	/// Bulk endpoint.
	Bulk,
	
	/// Interrupt endpoint.
	Interrupt,
}

This would be a breaking change, but it better reflects the USB specification.

How to create a language

Is there no public API to make an instance of Language? I couldn't find a way to make Language either from a raw hex or from using PrimaryLanguage and SecondaryLanguage.

rusb/src/language.rs

Lines 11 to 39 in f611e84

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Language {
raw: u16,
}
impl Language {
/// Returns the language's 16-bit `LANGID`.
///
/// Each language's `LANGID` is defined by the USB forum
/// (http://www.usb.org/developers/docs/USB_LANGIDs.pdf).
pub fn lang_id(self) -> u16 {
self.raw
}
/// Returns the primary language.
pub fn primary_language(self) -> PrimaryLanguage {
PrimaryLanguage::from_raw(self.raw)
}
/// Returns the sub language.
pub fn sub_language(self) -> SubLanguage {
SubLanguage::from_raw(self.primary_language(), self.raw)
}
}
#[doc(hidden)]
pub(crate) fn from_lang_id(raw: u16) -> Language {
Language { raw }
}

segmentation fault - EXC_BAD_ACCESS (code=1, address=0x714)

The following happens in releases 0.7 and 0.8 of rusb, but NOT in 0.6 (I am running MacOS Catalina 10.15.7) with the latest released versions of VSCode, MacOS dev tools, and Rust)

It shows up in code (for example probe-rs/probe-rs/cli ) and can be reproduced without any USB devices attached, by running probe-rs-cli list, after doing a cargo install probe-rs-cli

This is my call stack at the point of failure. Let me know if you need additional information.

pthread_mutex_lock (@pthread_mutex_lock:3)
usbi_mutex_lock (/Users/Jack/.cargo/registry/src/github.com-1ecc6299db9ec823/libusb1-sys-0.5.0/libusb/libusb/os/threads_posix.h:46)
libusb_unref_device (/Users/Jack/.cargo/registry/src/github.com-1ecc6299db9ec823/libusb1-sys-0.5.0/libusb/libusb/core.c:1194)
libusb_unref_device (/Users/Jack/.cargo/registry/src/github.com-1ecc6299db9ec823/libusb1-sys-0.5.0/libusb/libusb/core.c:1201)
process_new_device (/Users/Jack/dev/probe-rs/target/debug/build/libusb1-sys-29e9c85c70119d88/out/source/libusb-1.0.22/libusb/os/darwin_usb.c:1063)
darwin_scan_devices (/Users/Jack/dev/probe-rs/target/debug/build/libusb1-sys-29e9c85c70119d88/out/source/libusb-1.0.22/libusb/os/darwin_usb.c:1079)
darwin_init (/Users/Jack/dev/probe-rs/target/debug/build/libusb1-sys-29e9c85c70119d88/out/source/libusb-1.0.22/libusb/os/darwin_usb.c:543)
libusb_init (/Users/Jack/.cargo/registry/src/github.com-1ecc6299db9ec823/libusb1-sys-0.5.0/libusb/libusb/core.c:2316)
new (/Users/Jack/.cargo/registry/src/github.com-1ecc6299db9ec823/rusb-0.8.0/src/context.rs:209)
list_daplink_devices (/Users/Jack/dev/probe-rs/probe-rs/src/probe/daplink/tools.rs:15)
list_all (/Users/Jack/dev/probe-rs/probe-rs/src/probe/mod.rs:194)
list_connected_devices (/Users/Jack/dev/probe-rs/cli/src/main.rs:135)
main (/Users/Jack/dev/probe-rs/cli/src/main.rs:124)
call_once<fn() -> core::result::Result<(), anyhow::Error>,()> (/Users/Jack/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:227)
__rust_begin_short_backtrace<fn() -> core::result::Result<(), anyhow::Error>,core::result::Result<(), anyhow::Error>> (/Users/Jack/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:125)
{{closure}}<core::result::Result<(), anyhow::Error>> (/Users/Jack/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs:66)
core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once (@std::rt::lang_start_internal::h1faf79574185df6d:159)
std::panicking::try::do_call (@std::rt::lang_start_internal::h1faf79574185df6d:157)
std::panicking::try (@std::rt::lang_start_internal::h1faf79574185df6d:157)
std::panic::catch_unwind (@std::rt::lang_start_internal::h1faf79574185df6d:157)
lang_start_internal (@std::rt::lang_start_internal::h1faf79574185df6d:157)
lang_start<core::result::Result<(), anyhow::Error>> (/Users/Jack/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs:65)
main (@main:12)
start (@start:4)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.