rusb

repository·master·Indexed 19 days ago

https://github.com/a1ien/rusb

A safe Rust wrapper around the native libusb library for accessing USB devices. It utilizes RAII and Rust's lifetime system to provide a memory-safe interface for USB device communication, preventing common C-based issues like memory leaks and use-after-free errors. Supports Linux, macOS, and Windows.

Tokens
9.1K
Snippets
30
Records
45
Agent score
66%

What's inside rusb

  1. How rusb ensures memory and resource safety

    master

    The rusb crate provides a safe wrapper around the native libusb library by utilizing two core Rust principles:

    1. RAII (Resource Acquisition Is Initialization): Ensures that all acquired resources (like device handles or contexts) are automatically released when they go out of scope.
    2. Rust Lifetimes: Ensures that resources are released in the correct order and that references to resources do not outlive the resources themselves.

    This prevents common issues found in C-based USB programming, such as memory leaks or use-after-free errors.

  2. Install rusb via Cargo

    master

    To use rusb in your project, add it to your Cargo.toml dependencies. By default, rusb will automatically download the libusb source and build it for you, so no extra system setup is required.

    [dependencies]
    rusb = "0.9"
  3. Inspect USB interfaces and their descriptors

    master

    In rusb, a USB Interface represents a specific functional part of a device. An interface can have multiple descriptors, each representing an 'alternate setting' (different configurations for the same interface).

    You can use the Interface struct to retrieve its interface number and iterate through its available InterfaceDescriptors. Each descriptor provides details about the interface's class, subclass, protocol, and associated endpoints.

    // Conceptual usage pattern
    let interface_number = interface.number();
    
    for descriptor in interface.descriptors() {
        println!("Setting: {}", descriptor.setting_number());
        println!("Class: {}", descriptor.class_code());
        
        // Iterate over endpoints for this specific setting
        for endpoint in descriptor.endpoint_descriptors() {
            println!("Endpoint address: {:x}", endpoint.address());
        }
    }
  4. Manage hotplug callback lifetime with Registration

    master

    The Registration<T> struct manages the lifecycle of a hotplug callback. It uses the RAII pattern: when the Registration instance is dropped, the underlying libusb_hotplug_deregister_callback is automatically called to stop monitoring.

    To stop monitoring, you can either:

    1. Let the Registration object go out of scope.
    2. Explicitly drop the Registration object.
    3. Use Context::unregister_callback (if available in the context API).
  5. Initialize a USB context with `Context::new()` or `Context::with_options()`

    master

    To interact with USB devices using rusb, you must first create a Context.

    • Use Context::new() to create a standard new libusb context.
    • Use Context::with_options(&[UsbOption]) to create a context with specific runtime options applied.

    The Context type implements the UsbContext trait, which provides the primary interface for device discovery and management.

    use rusb::Context;
    
    // Create a new context
    let context = Context::new().expect("Failed to create context");
  6. Monitor USB device connection changes with Hotplug

    master

    To monitor when USB devices are connected or disconnected, implement the Hotplug trait and register it using a HotplugBuilder.

    Safety Constraints

    When handling events inside device_arrived or device_left:

    • Safe: Any function taking a Device. It is also safe to open a device and submit asynchronous transfers.
    • Unsafe: Most functions taking a DeviceHandle, such as synchronous API functions or blocking descriptor retrieval functions. These should be called outside the hotplug callback context.

    Implementation Steps

    1. Implement the Hotplug trait for your type.
    2. Use HotplugBuilder::new() to configure filters (vendor, product, or class ID).
    3. Call .enumerate(true) if you want device_arrived to be triggered for devices already connected at the time of registration.
    4. Call .register(context, callback) to start monitoring. This returns a Registration object.
    5. Important: The hotplug callback remains active only as long as the Registration object is in scope. When Registration is dropped, the callback is automatically deregistered.
    // Example implementation pattern
    struct MyHotplug;
    
    impl Hotplug<UsbContext> for MyHotplug {
        fn device_arrived(&mut self, device: Device<UsbContext>) {
            println!("Device arrived: {:?}", device);
        }
    
        fn device_left(&mut self, device: Device<UsbContext>) {
            println!("Device left: {:?}", device);
        }
    }
    
    fn main() -> Result<(), error::Error> {
        let context = UsbContext::new()?;
        let mut callback = Box::new(MyHotplug);
        
        // Register to monitor all devices
        let _registration = HotplugBuilder::new()
            .enumerate(true)
            .register(context, callback)?;
    
        // Keep the program running to listen for events
        loop { std::thread::sleep(std::time::Duration::from_secs(1)); }
    }
  7. Troubleshoot libusb build failures

    master

    If the automatic build of libusb fails, you can manually provide a native libusb installation. rusb can use a native library if it is discoverable via:

    • pkg-config
    • vcpkg

    rusb supports all systems supported by the native libusb library, including Linux, macOS, and Windows.

  8. List and inspect USB devices

    master

    The primary workflow in rusb begins with accessing the device list. You can iterate over available devices, access their descriptors to read metadata (like Vendor ID and Product ID), and retrieve bus/address information.

    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());
        }
    }
  9. Configure logging levels and callbacks

    master

    You can control the verbosity of libusb logs via the Context object.

    1. Set Log Level: Use set_log_level(level: LogLevel) to change the verbosity. Available levels are LogLevel::None, LogLevel::Error, LogLevel::Warning, LogLevel::Info, and LogLevel::Debug.
    2. Set Log Callback: Use set_log_callback to intercept logs. You must specify a LogCallbackMode:
      • LogCallbackMode::Global: Handles all log messages.
      • LogCallbackMode::Context: Handles logs related specifically to that context.
    use rusb::{Context, LogLevel, LogCallbackMode};
    
    let mut context = Context::new().unwrap();
    context.set_log_level(LogLevel::Debug);
    
    // Note: set_log_callback implementation details depend on providing a closure
    // context.set_log_callback(|level, msg| { ... }, LogCallbackMode::Context);
  10. Extract components from a `Language` instance

    master

    Once you have a Language instance, use the following methods to inspect its components:

    • lang_id() -> u16: Returns the original 16-bit LANGID.
    • primary_language() -> PrimaryLanguage: Returns the primary language family (e.g., English, French, Chinese).
    • sub_language() -> SubLanguage: Returns the specific dialect, region, or writing system (e.g., UnitedStates, Canada, Traditional).
    let lang = Language::new(0x0409);
    let id = lang.lang_id();
    let primary = lang.primary_language();
    let sub = lang.sub_language();
  11. Open a device for communication

    master

    To perform I/O operations (like reading or writing to endpoints), you must call .open() on a Device instance. This returns a DeviceHandle<T>, which is the object used for actual data transfer.

    let handle = device.open().expect("Failed to open device");