libusb 0.2.2

Rust library for accessing USB devices.
use std::marker::PhantomData;
use std::mem;
use std::slice;
use std::time::Duration;

use bit_set::BitSet;
use libc::{c_int,c_uint,c_uchar};

use ::context::Context;
use ::device_descriptor::DeviceDescriptor;
use ::config_descriptor::ConfigDescriptor;
use ::interface_descriptor::InterfaceDescriptor;
use ::fields::{Direction,RequestType,Recipient,request_type};
use ::language::Language;

/// A handle to an open USB device.
pub struct DeviceHandle<'a> {
    _context: PhantomData<&'a Context>,
    handle: *mut ::libusb::libusb_device_handle,
    interfaces: BitSet,
}

impl<'a> Drop for DeviceHandle<'a> {
    /// Closes the device.
    fn drop(&mut self) {
        unsafe {
            for iface in self.interfaces.iter() {
                ::libusb::libusb_release_interface(self.handle, iface as c_int);
            }

            ::libusb::libusb_close(self.handle);
        }
    }
}

impl<'a> DeviceHandle<'a> {
    /// Returns the active configuration number.
    pub fn active_configuration(&mut self) -> ::Result<u8> {
        let mut config = unsafe { mem::uninitialized() };

        try_unsafe!(::libusb::libusb_get_configuration(self.handle, &mut config));
        Ok(config as u8)
    }

    /// Sets the device's active configuration.
    pub fn set_active_configuration(&mut self, config: u8) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_set_configuration(self.handle, config as c_int));
        Ok(())
    }

    /// Puts the device in an unconfigured state.
    pub fn unconfigure(&mut self) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_set_configuration(self.handle, -1));
        Ok(())
    }

    /// Resets the device.
    pub fn reset(&mut self) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_reset_device(self.handle));
        Ok(())
    }

    /// Indicates whether the device has an attached kernel driver.
    ///
    /// This method is not supported on all platforms.
    pub fn kernel_driver_active(&mut self, iface: u8) -> ::Result<bool> {
        match unsafe { ::libusb::libusb_kernel_driver_active(self.handle, iface as c_int) } {
            0 => Ok(false),
            1 => Ok(true),
            err => Err(::error::from_libusb(err))
        }
    }

    /// Detaches an attached kernel driver from the device.
    ///
    /// This method is not supported on all platforms.
    pub fn detach_kernel_driver(&mut self, iface: u8) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_detach_kernel_driver(self.handle, iface as c_int));
        Ok(())
    }

    /// Attaches a kernel driver to the device.
    ///
    /// This method is not supported on all platforms.
    pub fn attach_kernel_driver(&mut self, iface: u8) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_attach_kernel_driver(self.handle, iface as c_int));
        Ok(())
    }

    /// Claims one of the device's interfaces.
    ///
    /// An interface must be claimed before operating on it. All claimed interfaces are released
    /// when the device handle goes out of scope.
    pub fn claim_interface(&mut self, iface: u8) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_claim_interface(self.handle, iface as c_int));
        self.interfaces.insert(iface as usize);
        Ok(())
    }

    /// Releases a claimed interface.
    pub fn release_interface(&mut self, iface: u8) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_release_interface(self.handle, iface as c_int));
        self.interfaces.remove(&(iface as usize));
        Ok(())
    }

    /// Sets an interface's active setting.
    pub fn set_alternate_setting(&mut self, iface: u8, setting: u8) -> ::Result<()> {
        try_unsafe!(::libusb::libusb_set_interface_alt_setting(self.handle, iface as c_int, setting as c_int));
        Ok(())
    }

    /// Reads from an interrupt endpoint.
    ///
    /// This function attempts to read from the interrupt endpoint with the address given by the
    /// `endpoint` parameter and fills `buf` with any data received from the endpoint. The function
    /// blocks up to the amount of time specified by `timeout`.
    ///
    /// If the return value is `Ok(n)`, then `buf` is populated with `n` bytes of data received
    /// from the endpoint.
    ///
    /// ## Errors
    ///
    /// If this function encounters any form of error while fulfilling the transfer request, an
    /// error variant will be returned. If an error variant is returned, no bytes were read.
    ///
    /// The errors returned by this function include:
    ///
    /// * `InvalidParam` if the endpoint is not an input endpoint.
    /// * `Timeout` if the transfer timed out.
    /// * `Pipe` if the endpoint halted.
    /// * `Overflow` if the device offered more data.
    /// * `NoDevice` if the device has been disconnected.
    /// * `Io` if the transfer encountered an I/O error.
    pub fn read_interrupt(&mut self, endpoint: u8, buf: &mut [u8], timeout: Duration) -> ::Result<usize> {
        if endpoint & ::libusb::LIBUSB_ENDPOINT_DIR_MASK != ::libusb::LIBUSB_ENDPOINT_IN {
            return Err(::Error::InvalidParam);
        }

        let mut transferred: c_int = unsafe { mem::uninitialized() };

        let ptr = buf.as_mut_ptr() as *mut c_uchar;
        let len = buf.len() as c_int;
        let timeout_ms = (timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000) as c_uint;

        match unsafe { ::libusb::libusb_interrupt_transfer(self.handle, endpoint, ptr, len, &mut transferred, timeout_ms) } {
            0 => {
                Ok(transferred as usize)
            },
            err => {
                if err == ::libusb::LIBUSB_ERROR_INTERRUPTED && transferred > 0 {
                    Ok(transferred as usize)
                }
                else {
                    Err(::error::from_libusb(err))
                }
            },
        }
    }

    /// Writes to an interrupt endpoint.
    ///
    /// This function attempts to write the contents of `buf` to the interrupt endpoint with the
    /// address given by the `endpoint` parameter. The function blocks up to the amount of time
    /// specified by `timeout`.
    ///
    /// If the return value is `Ok(n)`, then `n` bytes of `buf` were written to the endpoint.
    ///
    /// ## Errors
    ///
    /// If this function encounters any form of error while fulfilling the transfer request, an
    /// error variant will be returned. If an error variant is returned, no bytes were written.
    ///
    /// The errors returned by this function include:
    ///
    /// * `InvalidParam` if the endpoint is not an output endpoint.
    /// * `Timeout` if the transfer timed out.
    /// * `Pipe` if the endpoint halted.
    /// * `NoDevice` if the device has been disconnected.
    /// * `Io` if the transfer encountered an I/O error.
    pub fn write_interrupt(&mut self, endpoint: u8, buf: &[u8], timeout: Duration) -> ::Result<usize> {
        if endpoint & ::libusb::LIBUSB_ENDPOINT_DIR_MASK != ::libusb::LIBUSB_ENDPOINT_OUT {
            return Err(::Error::InvalidParam);
        }

        let mut transferred: c_int = unsafe { mem::uninitialized() };

        let ptr = buf.as_ptr() as *mut c_uchar;
        let len = buf.len() as c_int;
        let timeout_ms = (timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000) as c_uint;

        match unsafe { ::libusb::libusb_interrupt_transfer(self.handle, endpoint, ptr, len, &mut transferred, timeout_ms) } {
            0 => {
                Ok(transferred as usize)
            },
            err => {
                if err == ::libusb::LIBUSB_ERROR_INTERRUPTED && transferred > 0 {
                    Ok(transferred as usize)
                }
                else {
                    Err(::error::from_libusb(err))
                }
            },
        }
    }

    /// Reads from a bulk endpoint.
    ///
    /// This function attempts to read from the bulk endpoint with the address given by the
    /// `endpoint` parameter and fills `buf` with any data received from the endpoint. The function
    /// blocks up to the amount of time specified by `timeout`.
    ///
    /// If the return value is `Ok(n)`, then `buf` is populated with `n` bytes of data received
    /// from the endpoint.
    ///
    /// ## Errors
    ///
    /// If this function encounters any form of error while fulfilling the transfer request, an
    /// error variant will be returned. If an error variant is returned, no bytes were read.
    ///
    /// The errors returned by this function include:
    ///
    /// * `InvalidParam` if the endpoint is not an input endpoint.
    /// * `Timeout` if the transfer timed out.
    /// * `Pipe` if the endpoint halted.
    /// * `Overflow` if the device offered more data.
    /// * `NoDevice` if the device has been disconnected.
    /// * `Io` if the transfer encountered an I/O error.
    pub fn read_bulk(&mut self, endpoint: u8, buf: &mut [u8], timeout: Duration) -> ::Result<usize> {
        if endpoint & ::libusb::LIBUSB_ENDPOINT_DIR_MASK != ::libusb::LIBUSB_ENDPOINT_IN {
            return Err(::Error::InvalidParam);
        }

        let mut transferred: c_int = unsafe { mem::uninitialized() };

        let ptr = buf.as_mut_ptr() as *mut c_uchar;
        let len = buf.len() as c_int;
        let timeout_ms = (timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000) as c_uint;

        match unsafe { ::libusb::libusb_bulk_transfer(self.handle, endpoint, ptr, len, &mut transferred, timeout_ms) } {
            0 => {
                Ok(transferred as usize)
            },
            err => {
                if err == ::libusb::LIBUSB_ERROR_INTERRUPTED && transferred > 0 {
                    Ok(transferred as usize)
                }
                else {
                    Err(::error::from_libusb(err))
                }
            },
        }
    }

    /// Writes to a bulk endpoint.
    ///
    /// This function attempts to write the contents of `buf` to the bulk endpoint with the address
    /// given by the `endpoint` parameter. The function blocks up to the amount of time specified
    /// by `timeout`.
    ///
    /// If the return value is `Ok(n)`, then `n` bytes of `buf` were written to the endpoint.
    ///
    /// ## Errors
    ///
    /// If this function encounters any form of error while fulfilling the transfer request, an
    /// error variant will be returned. If an error variant is returned, no bytes were written.
    ///
    /// The errors returned by this function include:
    ///
    /// * `InvalidParam` if the endpoint is not an output endpoint.
    /// * `Timeout` if the transfer timed out.
    /// * `Pipe` if the endpoint halted.
    /// * `NoDevice` if the device has been disconnected.
    /// * `Io` if the transfer encountered an I/O error.
    pub fn write_bulk(&mut self, endpoint: u8, buf: &[u8], timeout: Duration) -> ::Result<usize> {
        if endpoint & ::libusb::LIBUSB_ENDPOINT_DIR_MASK != ::libusb::LIBUSB_ENDPOINT_OUT {
            return Err(::Error::InvalidParam);
        }

        let mut transferred: c_int = unsafe { mem::uninitialized() };

        let ptr = buf.as_ptr() as *mut c_uchar;
        let len = buf.len() as c_int;
        let timeout_ms = (timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000) as c_uint;

        match unsafe { ::libusb::libusb_bulk_transfer(self.handle, endpoint, ptr, len, &mut transferred, timeout_ms) } {
            0 => {
                Ok(transferred as usize)
            },
            err => {
                if err == ::libusb::LIBUSB_ERROR_INTERRUPTED && transferred > 0 {
                    Ok(transferred as usize)
                }
                else {
                    Err(::error::from_libusb(err))
                }
            },
        }
    }

    /// Reads data using a control transfer.
    ///
    /// This function attempts to read data from the device using a control transfer and fills
    /// `buf` with any data received during the transfer. The function blocks up to the amount of
    /// time specified by `timeout`.
    ///
    /// The parameters `request_type`, `request`, `value`, and `index` specify the fields of the
    /// control transfer setup packet (`bmRequestType`, `bRequest`, `wValue`, and `wIndex`
    /// respectively). The values for each of these parameters shall be given in host-endian byte
    /// order. The value for the `request_type` parameter can be built with the helper function,
    /// [request_type()](fn.request_type.html). The meaning of the other parameters depends on the
    /// type of control request.
    ///
    /// If the return value is `Ok(n)`, then `buf` is populated with `n` bytes of data.
    ///
    /// ## Errors
    ///
    /// If this function encounters any form of error while fulfilling the transfer request, an
    /// error variant will be returned. If an error variant is returned, no bytes were read.
    ///
    /// The errors returned by this function include:
    ///
    /// * `InvalidParam` if `request_type` does not specify a read transfer.
    /// * `Timeout` if the transfer timed out.
    /// * `Pipe` if the control request was not supported by the device.
    /// * `NoDevice` if the device has been disconnected.
    /// * `Io` if the transfer encountered an I/O error.
    pub fn read_control(&mut self, request_type: u8, request: u8, value: u16, index: u16, buf: &mut [u8], timeout: Duration) -> ::Result<usize> {
        if request_type & ::libusb::LIBUSB_ENDPOINT_DIR_MASK != ::libusb::LIBUSB_ENDPOINT_IN {
            return Err(::Error::InvalidParam);
        }

        let ptr = buf.as_mut_ptr() as *mut c_uchar;
        let len = buf.len() as u16;
        let timeout_ms = (timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000) as c_uint;

        let res = unsafe {
            ::libusb::libusb_control_transfer(self.handle, request_type, request, value, index, ptr, len, timeout_ms)
        };

        if res < 0 {
            Err(::error::from_libusb(res))
        } else {
            Ok(res as usize)
        }
    }

    /// Writes data using a control transfer.
    ///
    /// This function attempts to write the contents of `buf` to the device using a control
    /// transfer. The function blocks up to the amount of time specified by `timeout`.
    ///
    /// The parameters `request_type`, `request`, `value`, and `index` specify the fields of the
    /// control transfer setup packet (`bmRequestType`, `bRequest`, `wValue`, and `wIndex`
    /// respectively). The values for each of these parameters shall be given in host-endian byte
    /// order. The value for the `request_type` parameter can be built with the helper function,
    /// [request_type()](fn.request_type.html). The meaning of the other parameters depends on the
    /// type of control request.
    ///
    /// If the return value is `Ok(n)`, then `n` bytes of `buf` were transfered.
    ///
    /// ## Errors
    ///
    /// If this function encounters any form of error while fulfilling the transfer request, an
    /// error variant will be returned. If an error variant is returned, no bytes were read.
    ///
    /// The errors returned by this function include:
    ///
    /// * `InvalidParam` if `request_type` does not specify a write transfer.
    /// * `Timeout` if the transfer timed out.
    /// * `Pipe` if the control request was not supported by the device.
    /// * `NoDevice` if the device has been disconnected.
    /// * `Io` if the transfer encountered an I/O error.
    pub fn write_control(&mut self, request_type: u8, request: u8, value: u16, index: u16, buf: &[u8], timeout: Duration) -> ::Result<usize> {
        if request_type & ::libusb::LIBUSB_ENDPOINT_DIR_MASK != ::libusb::LIBUSB_ENDPOINT_OUT {
            return Err(::Error::InvalidParam);
        }

        let ptr = buf.as_ptr() as *mut c_uchar;
        let len = buf.len() as u16;
        let timeout_ms = (timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000) as c_uint;

        let res = unsafe {
            ::libusb::libusb_control_transfer(self.handle, request_type, request, value, index, ptr, len, timeout_ms)
        };

        if res < 0 {
            Err(::error::from_libusb(res))
        } else {
            Ok(res as usize)
        }
    }

    /// Reads the languages supported by the device's string descriptors.
    ///
    /// This function returns a list of languages that can be used to read the device's string
    /// descriptors.
    pub fn read_languages(&mut self, timeout: Duration) -> ::Result<Vec<Language>> {
        let mut buf = Vec::<u8>::with_capacity(256);

        let mut buf_slice = unsafe {
            slice::from_raw_parts_mut((&mut buf[..]).as_mut_ptr(), buf.capacity())
        };

        let len = try!(self.read_control(request_type(Direction::In, RequestType::Standard, Recipient::Device),
                                         ::libusb::LIBUSB_REQUEST_GET_DESCRIPTOR,
                                         (::libusb::LIBUSB_DT_STRING as u16) << 8,
                                         0,
                                         buf_slice,
                                         timeout));

        unsafe {
            buf.set_len(len);
        }

        Ok(buf.chunks(2).skip(1).map(|chunk| {
            let lang_id = chunk[0] as u16 | (chunk[1] as u16) << 8;
            ::language::from_lang_id(lang_id)
        }).collect())
    }

    /// Reads a string descriptor from the device.
    ///
    /// `language` should be one of the languages returned from [`read_languages`](#method.read_languages).
    pub fn read_string_descriptor(&mut self, language: Language, index: u8, timeout: Duration) -> ::Result<String> {
        let mut buf = Vec::<u8>::with_capacity(256);

        let mut buf_slice = unsafe {
            slice::from_raw_parts_mut((&mut buf[..]).as_mut_ptr(), buf.capacity())
        };

        let len = try!(self.read_control(request_type(Direction::In, RequestType::Standard, Recipient::Device),
                                         ::libusb::LIBUSB_REQUEST_GET_DESCRIPTOR,
                                         (::libusb::LIBUSB_DT_STRING as u16) << 8 | index as u16,
                                         language.lang_id(),
                                         buf_slice,
                                         timeout));

        unsafe {
            buf.set_len(len);
        }

        let utf16: Vec<u16> = buf.chunks(2).skip(1).map(|chunk| {
            chunk[0] as u16 | (chunk[1] as u16) << 8
        }).collect();

        String::from_utf16(&utf16[..]).map_err(|_| ::error::Error::Other)
    }

    /// Reads the device's manufacturer string descriptor.
    pub fn read_manufacturer_string(&mut self, language: Language, device: &DeviceDescriptor, timeout: Duration) -> ::Result<String> {
        match device.manufacturer_string_index() {
            None => Err(::Error::InvalidParam),
            Some(n) => self.read_string_descriptor(language, n, timeout)
        }
    }

    /// Reads the device's product string descriptor.
    pub fn read_product_string(&mut self, language: Language, device: &DeviceDescriptor, timeout: Duration) -> ::Result<String> {
        match device.product_string_index() {
            None => Err(::Error::InvalidParam),
            Some(n) => self.read_string_descriptor(language, n, timeout)
        }
    }

    /// Reads the device's serial number string descriptor.
    pub fn read_serial_number_string(&mut self, language: Language, device: &DeviceDescriptor, timeout: Duration) -> ::Result<String> {
        match device.serial_number_string_index() {
            None => Err(::Error::InvalidParam),
            Some(n) => self.read_string_descriptor(language, n, timeout)
        }
    }

    /// Reads the string descriptor for a configuration's description.
    pub fn read_configuration_string(&mut self, language: Language, configuration: &ConfigDescriptor, timeout: Duration) -> ::Result<String> {
        match configuration.description_string_index() {
            None => Err(::Error::InvalidParam),
            Some(n) => self.read_string_descriptor(language, n, timeout)
        }
    }

    /// Reads the string descriptor for a interface's description.
    pub fn read_interface_string(&mut self, language: Language, interface: &InterfaceDescriptor, timeout: Duration) -> ::Result<String> {
        match interface.description_string_index() {
            None => Err(::Error::InvalidParam),
            Some(n) => self.read_string_descriptor(language, n, timeout)
        }
    }
}

#[doc(hidden)]
pub fn from_libusb<'a>(context: PhantomData<&'a Context>, handle: *mut ::libusb::libusb_device_handle) -> DeviceHandle<'a> {
    DeviceHandle {
        _context: context,
        handle: handle,
        interfaces: BitSet::with_capacity(u8::max_value() as usize + 1),
    }
}