dcuddeback / libusb-rs

A safe Rust wrapper for libusb.
MIT License
199 stars 64 forks source link

read_languages() returns Err(Overflow) #21

Closed Boscop closed 6 years ago

Boscop commented 6 years ago

I'm trying to use the read_device() function from the example, but read_languages() returns Err(Overflow). Why?

Btw, the device I'm trying to read is a Corsair K95 RGB keyboard:

Bus 002 Device 009 ID 1b1c:1b11   12 Mbps
Device Descriptor:
  bcdUSB              2.00
  bDeviceClass      0x00
  bDeviceSubClass    0x00
  bDeviceProtocol    0x00
  bMaxPacketSize0      64
  idVendor        0x1b1c
  idProduct      0x1b11
  bcdDevice        2.05
  iManufacturer       1 
  iProduct             2 
  iSerialNumber       3 
  bNumConfigurations     1
  Config Descriptor:
    bNumInterfaces       3
    bConfigurationValue    1
    iConfiguration       0 
    bmAttributes:
      Self Powered   false
      Remote Wakeup  true
    bMaxPower           500mW
    Interface Descriptor:
      bInterfaceNumber     0
      bAlternateSetting   0
      bNumEndpoints       1
      bInterfaceClass    0x03
      bInterfaceSubClass  0x01
      bInterfaceProtocol  0x01
      iInterface             0 
      Endpoint Descriptor:
        bEndpointAddress    0x81 EP 1 In
        bmAttributes:
          Transfer Type       Interrupt
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize  0x0008
        bInterval             8
    Interface Descriptor:
      bInterfaceNumber     1
      bAlternateSetting   0
      bNumEndpoints       1
      bInterfaceClass    0x03
      bInterfaceSubClass  0x00
      bInterfaceProtocol  0x00
      iInterface             0 
      Endpoint Descriptor:
        bEndpointAddress    0x82 EP 2 In
        bmAttributes:
          Transfer Type       Interrupt
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize  0x0040
        bInterval             1
    Interface Descriptor:
      bInterfaceNumber     2
      bAlternateSetting   0
      bNumEndpoints       1
      bInterfaceClass    0x03
      bInterfaceSubClass  0x00
      bInterfaceProtocol  0x00
      iInterface             0 
      Endpoint Descriptor:
        bEndpointAddress    0x03 EP 3 Out
        bmAttributes:
          Transfer Type       Interrupt
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize  0x0040
        bInterval             1
dcuddeback commented 6 years ago

@Boscop Interesting. So the libusb documentation says it returns:

LIBUSB_ERROR_OVERFLOW if the device offered more data

but only for bulk and interrupt transfers. http://libusb.sourceforge.net/api-1.0/packetoverflow.html also suggests it's only an issue for bulk/interrupt transfers. Getting the list of languages is a control transfer.

read_languages() uses a 256 byte buffer to read the language IDs, which should be enough because the descriptor's length is encoded as a byte (max value 255): http://www.beyondlogic.org/usbnutshell/usb5.shtml#StringDescriptors.

I'm guessing you could get it to work by increasing the buffer size beyond a certain point, but I'd like to understand the root cause. If you get it to work that way, I'd be curious to know the minimum buffer size for it to work and what the length of the returned data is.

Have you tried with more than one operating system? Based on the other tickets you've opened, I'm assuming you're trying this on Windows with the MSVC toolchain. Do you get the same result on Linux?

Boscop commented 6 years ago

I will test it more extensively, for now I commented out the call to read_languages() in read_device() from the example, and now I get this output:

Active configuration: 1
Reading from endpoint: Endpoint { config: 1, iface: 0, setting: 0, address: 129 }
 - kernel driver? false
could not read from endpoint: Input/Output Error
No readable bulk endpoint

But it should be possible to read from the device (keyboard), someone did it here: https://github.com/sverrirs/CorsairK95/blob/master/CorsairK95.NET/K95Tester/K95Device.cs#L187-L237 (from this blog post)

What am I doing wrong?

Boscop commented 6 years ago

I'm trying to port this to rust (sending LED data to the RGB keyboard). I'm doing the exact same thing as that lib now, but for me, calling write_control() always returns Err(Io), why? How can I get more info about the cause of the error?

This is my code:

extern crate libusb;
#[macro_use] extern crate smart_default;

use std::time::Duration;

fn main() {
    let (vid, pid) = (0x1B1C, 0x1B11);
    match libusb::Context::new() {
        Ok(mut context) => {
            match open_device(&mut context, vid, pid) {
                Some((mut device, device_desc, mut handle)) => {
                    let mut kbd = K95::new();
                    kbd.set_led(CLK_F1, 255, 0, 0);
                    println!("{:?}", kbd.flush(&mut handle));
                }
                None => println!("could not find device {:04x}:{:04x}", vid, pid)
            }
        },
        Err(e) => panic!("could not initialize libusb: {}", e)
    }
}

#[derive(SmartDefault)]
struct K95 {
    #[default = "[0; 144]"]
    red_val: [u8; 144],
    #[default = "[0; 144]"]
    grn_val: [u8; 144],
    #[default = "[0; 144]"]
    blu_val: [u8; 144],
    #[default = "[[0; 64]; 5]"]
    data_pkt: [[u8; 64]; 5],
}

impl K95 {
    fn new() -> Self {
        let mut s = Self::default();

        s.data_pkt[0][0] = 0x7F;
        s.data_pkt[0][1] = 0x01;
        s.data_pkt[0][2] = 0x3C;

        s.data_pkt[1][0] = 0x7F;
        s.data_pkt[1][1] = 0x02;
        s.data_pkt[1][2] = 0x3C;

        s.data_pkt[2][0] = 0x7F;
        s.data_pkt[2][1] = 0x03;
        s.data_pkt[2][2] = 0x3C;

        s.data_pkt[3][0] = 0x7F;
        s.data_pkt[3][1] = 0x04;
        s.data_pkt[3][2] = 0x24;

        s.data_pkt[4][0] = 0x07;
        s.data_pkt[4][1] = 0x27;
        s.data_pkt[4][4] = 0xD8;

        s
    }
    fn set_led(&mut self, key: CorsairLedId, r: u8, g: u8, b: u8) {
        let key_code = KEYMAP_US[key as usize] as usize;
        if key_code == 0xFF { return; }
        // For some reason rgb values are only 0-7? Not quite 16.8 million colours...
        // 256 / 32 = 8
        self.red_val[key_code] = 7 - (r >> 5);
        self.grn_val[key_code] = 7 - (g >> 5);
        self.blu_val[key_code] = 7 - (b >> 5);
    }
    fn flush(&mut self, handle: &mut libusb::DeviceHandle) -> libusb::Result<()> {
        // Perform USB control message to keyboard
        //
        // Request Type:  0x21
        // Request:       0x09
        // Value          0x0300
        // Index:         0x03
        // Size:          64

        for i in 0..60 {
            self.data_pkt[0][i+4] = self.red_val[i*2+1] << 4 | self.red_val[i*2];
        }

        for i in 0..12 {
            self.data_pkt[1][i+4] = self.red_val[i*2+121] << 4 | self.red_val[i*2+120];
        }

        for i in 0..48 {
            self.data_pkt[1][i+16] = self.grn_val[i*2+1] << 4 | self.grn_val[i*2];
        }

        for i in 0..24 {
            self.data_pkt[2][i+4] = self.grn_val[i*2+97] << 4 | self.grn_val[i*2+96];
        }

        for i in 0..36 {
            self.data_pkt[2][i+28] = self.blu_val[i*2+1] << 4 | self.blu_val[i*2];
        }

        for i in 0..36 {
            self.data_pkt[3][i+4] = self.blu_val[i*2+73] << 4 | self.blu_val[i*2+72];
        }

        let timeout = Duration::from_secs(1);
        handle.write_control(0x21, 0x09, 0x0300, 0x03, &self.data_pkt[0], timeout)?;
        handle.write_control(0x21, 0x09, 0x0300, 0x03, &self.data_pkt[1], timeout)?;
        handle.write_control(0x21, 0x09, 0x0300, 0x03, &self.data_pkt[2], timeout)?;
        handle.write_control(0x21, 0x09, 0x0300, 0x03, &self.data_pkt[3], timeout)?;
        handle.write_control(0x21, 0x09, 0x0300, 0x03, &self.data_pkt[4], timeout)?;
        Ok(())
    }
}

I'm always getting Err(Io) on the first call to write_control().

(Btw, here is another program that writes the LED data successfully.)

dcuddeback commented 6 years ago

@Boscop Did you ever get to the bottom of the issue you're having? I can't tell from your comments whether you're reporting a bug or not, and you didn't answer any of my questions. I don't have the time to read through all the code you've pasted to help you debug your program. If you've found a bug in this library, would you mind pasting a minimal code sample to reproduce the issue? This ticket isn't actionable as is.

Boscop commented 6 years ago

Sorry, I didn't get to the bottom yet, was busy with other things...

I think the cause of this IO error could be same as the one I'm getting when trying to open the keyboard with the hidapi crate: https://github.com/signal11/hidapi/issues/247#issuecomment-161152387

It's a security problem if a normally-privileged application can keystroke-log the system keyboard. Windows won't allow you to open a keyboard with hidd.dll.

lespea commented 4 years ago

It appears that values over 255 are problematic on windows:

https://github.com/libusb/libusb/blob/93dcb8ed205a4e4cea105c2141fbbbdeac84bb66/libusb/descriptor.c#L1144

https://github.com/libusb/libusb/blob/4b94eeddadf46cd046acef4e36969d97ab4bb31a/libusb/os/windows_winusb.c#L3190