# Hack Gaming Keyboards on Linux
## ~~方針~~
- ~~`libusb`というCのライブラリをRustから呼び出して,ゲーミングキーボードの光り方を制御する信号を送信する.~~
- ~~USBの信号をWiresharkでキャプチャ.どんな信号で光り方を制御してるかを把握する.~~
- ~~それっぽい信号を`libusb`経由で入力する.~~
## キーボードで押されたキーをRustの`println!`で出してみる
1. 入力されたフレームのペイロードを取り出す関数を作る
2. ペイロードから入力キーを判定する関数を作る
3. 2.の関数で得た文字を`print!`する
### USB から取得できるディスクリプタ一覧
1. Device descriptor
2. Config descriptor
3. String descriptor
Device descriptor中のiProductとかがこれのインデックスになっている
```rust
// 以下はイメージ
let product_name = string_descriptor[device_descriptor.iProduct]
```
5. Interface descriptor
[参考](https://wiki.onakasuita.org/pukiwiki/?USB%2F%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%82%BF)
### `lsusb`コマンドの動作
1. USBデバイスの`vendor_id`と`product_id`を取得する.
2. `/usr/lib/udev/hwdb.d/20-usb-vendor-model.hwdb`から以下を取得する.
- `vendor_id` -> vendor_name
- `product_id` -> product_name ()
3. vendor nameとproduct nameを標準出力に表示する.
- `bus_number`, `address`も併せて表示している
例:
```
Bus 001 Device 004: ID 8087:0a2b Intel Corp. Bluetooth wireless interface
```
Intel Corp. は20-usb-vendor-model.hwdb中のv8087の`ID_VENDOR_FROM_DATABASE=Intel Corp.`、Bluetooth wirelessは`v8087p0A2BのID_MODEL_FROM_DATABASE=Bluetooth wireless interface`から取得している。=> string descriptor の内容と一致しない!
なお、このUSBデバイスはstring descriptorを持たないので,rusbで読もうとすると失敗する.
## Microdia USB DEVICE (tezukaのキーボード)
`sudo lsusb -d 0c45:7680 -v`
* Device Descriptor
idVendor: 0c45 Microdia
idProduct: 7680
string_descriptor[iProducr]: USB DEVICE
* Configuration Descriptor
* Interface Descriptor
bInterfaceClass: 3 Human Interface Device
bInterfaceProtocol: 1 Keyboard
* Endpoint Descriptor
bEndpoint Address: 0x81 EP 1 IN
* bmAttributes: 3
TransferType: Interrupt
Synch Type: None
Usage Type: Data
* Interface Descriptor
bInterfaceClass: 3 Human Interface Device
bInterfaceProtocol: 2 Mouse
* Endpoint Descriptor
bEndpoint Address: 0x82 EP 2 IN
* bmAttributes: 3
TransferType: Interrupt
Synch Type: None
Usage Type: Data
## USBの転送方式
1. Interrupt
e.g. キーボード,マウス
人間が遅延を感じない程度の感覚でホストPCがデバイスにデータ転送要求を送る(USBの使用上、ホストが要求を送らないとデータ転送が始まらない)。
3. Bulk
e.g. プリンタ,スキャナ
転送時間に制約を設けない。データの信頼性を重視。
4. Isochronous
e.g. マイク,内カメラ
一定周期で一定量のデータを送る
5. Control
デバイスの情報のやり取りやUSB環境下でのアドレス設定、デバイスの設定などに利用される。
[参考](https://www.renesas.com/jp/ja/application/key-technology/usb-technology/usb1-1)
## configure_endpoint関数で呼ばれているメソッドのソースコード
いずれも,libusb内で[usbi_os_backend構造体型](https://github.com/libusb/libusb/blob/f3619c4078a921c3d6bf3238ff7a8dc70d60e40e/libusb/libusbi.h#L1454)の変数usbi_backendのメソッドとして定義されている.usbi_os_backend構造体がOSごとの差を吸収していて,Linux向けの実装は[ここ](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.c#L2768)にある.
rusbソースコードに頻出のtry_unsafe!マクロの[定義](https://github.com/a1ien/rusb/blob/f611e84804723c73c6b6993c73b5bfa2c44ab025/src/error.rs#L97)
1. [set_active_configuration (rusb)](https://docs.rs/rusb/latest/src/rusb/device_handle.rs.html#212) / [libusb_set_configuration (libusb)](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/core.c#L1733)
中身は[op_set_configuration](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.c#L1462)
重要なのは次の4行
```C
struct linux_device_priv *priv = usbi_get_device_priv(handle->dev);
struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
int fd = hpriv->fd;
int r = ioctl(fd, IOCTL_USBFS_SETCONFIGURATION, &config);
```
[usbi_get_device_priv](https://github.com/libusb/libusb/blob/f3619c4078a921c3d6bf3238ff7a8dc70d60e40e/libusb/libusbi.h#L870)
[usbi_get_device_handle_priv](https://github.com/libusb/libusb/blob/f3619c4078a921c3d6bf3238ff7a8dc70d60e40e/libusb/libusbi.h#L875)
[IOCTL_USBFS_SETCONFIGURATION](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.h#L140)
ioctlは #include <sys/ioctl.h>
2. [claim_interface (rusb)](https://docs.rs/rusb/latest/src/rusb/device_handle.rs.html#286) / [libusb_claim_interface (libusb)](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/core.c#L1770)
中身は[op_claim_interface](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.c#L1824)
重要なのは次の4行
```C
if (handle->auto_detach_kernel_driver)
return detach_kernel_driver_and_claim(handle, interface);
else
return claim_interface(handle, interface);
```
[detach_kernel_driver_and_claim](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.c#L1788)の中身の一部
```C
dc.flags = USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER;
r = ioctl(fd, IOCTL_USBFS_DISCONNECT_CLAIM, &dc);
```
[IOCTL_USBFS_DISCONNECT_CLAIM](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.h#L154)
[claim_interface](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.c#L1492)の中身の一部
```C
struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
int fd = hpriv->fd;
int r = ioctl(fd, IOCTL_USBFS_CLAIMINTERFACE, &iface);
```
[IOCTL_USBFS_CLAIMINTERFACE](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.h#L145)
5. [set_alternate_setting (rusb)](https://docs.rs/rusb/latest/src/rusb/device_handle.rs.html#303) / [libusb_set_interface_alt_setting (libusb)](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/core.c#L1859)
## detach_kernel_driver
[detach_kernel_driver (rusb)](https://docs.rs/rusb/latest/src/rusb/device_handle.rs.html#250) / [libusb_detach_kernel_driver (libusb)](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/core.c#L2119)
中身は[op_detach_kernel_driver](https://github.com/libusb/libusb/blob/9e077421b8708d98c8d423423bd6678dca0ef2ae/libusb/os/linux_usbfs.c#L1722)
重要なのは次の行
```cpp
struct usbfs_ioctl command;
struct usbfs_getdriver getdrv;
int r;
command.ifno = interface;
command.ioctl_code = IOCTL_USBFS_DISCONNECT;
command.data = NULL;
getdrv.interface = interface;
r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv);
...
r = ioctl(fd, IOCTL_USBFS_IOCTL, &command);
```
## read_interrupt
[read_interrupt (rusb)](https://docs.rs/rusb/latest/src/rusb/device_handle.rs.html#335) / [libusb_interrupt_transfer (libusb)](https://github.com/libusb/libusb/blob/c060e9ce30ac2e3ffb49d94209c4dae77b6642f7/libusb/sync.c#L328)
中身は[do_sync_bulk_transfer](https://github.com/libusb/libusb/blob/c060e9ce30ac2e3ffb49d94209c4dae77b6642f7/libusb/sync.c#L169)
```cpp
transfer = libusb_alloc_transfer(0);
if (!transfer)
return LIBUSB_ERROR_NO_MEM;
libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length,
sync_transfer_cb, &completed, timeout);
transfer->type = type;
r = libusb_submit_transfer(transfer);
if (r < 0) {
libusb_free_transfer(transfer);
return r;
}
sync_transfer_wait_for_completion(transfer);
...
libusb_free_transfer(transfer);
```
[libusb_alloc_transfer](https://github.com/libusb/libusb/blob/54350bd83fbcc9555abc57988d6fd73f3b9e9ff8/libusb/libusb.h#L1765)
[libusb_fill_bulk_transfer](https://github.com/libusb/libusb/blob/54350bd83fbcc9555abc57988d6fd73f3b9e9ff8/libusb/libusb.h#L1833)
transfer変数の各メンバに値を代入しているだけ
[libusb_submit_transfer](https://github.com/libusb/libusb/blob/54350bd83fbcc9555abc57988d6fd73f3b9e9ff8/libusb/libusb.h#L1766)
[libusb_free_transfer](https://github.com/libusb/libusb/blob/54350bd83fbcc9555abc57988d6fd73f3b9e9ff8/libusb/libusb.h#L1768)
[libusb_transfer構造体](https://github.com/libusb/libusb/blob/54350bd83fbcc9555abc57988d6fd73f3b9e9ff8/libusb/libusb.h#L1329)
[LIBUSB_CALL](https://github.com/libusb/libusb/blob/54350bd83fbcc9555abc57988d6fd73f3b9e9ff8/libusb/libusb.h#L89)
[sync_transfer_wait_for_completion](https://github.com/libusb/libusb/blob/c060e9ce30ac2e3ffb49d94209c4dae77b6642f7/libusb/sync.c#L43)
## キーボードのUSBプロトコル解析
WiresharkでUSB信号をモニター
hostからキーボードへInterrupt要求を送る
↓
待機
↓
キーが**押される**とキーボードからhostへデータが送られる
↓
hostは受信直後にまたInterrupt要求を送る(キーが押され続けている間はこれに対するデータ返送はない)
↓
キーが**離される**とキーボードからhostへデータが送られる
↓
始めに戻る
キーの押された/離されたデータは1フレームで送られ,その大きさは固定
USB HID(Human Interface Device)プロトコル公式資料は[ここ](https://usb.org/sites/default/files/hut1_4.pdf)
ただし,IchinoseのキーボードはN-key rollover対応であり,通常のUSB HIDプロトコルではなく,押されたキーに対応するビットを立てる方式である.
例:a,s,d,f,j,k,l,;キーを同時に押すと,060090e240000000080000000000000000000000000000000000000000000000
## 包括的な情報源
[USB Concepts](https://www.keil.com/pack/doc/mw/USB/html/_u_s_b__concepts.html)
[libusb公式APIリファレンス](https://libusb.sourceforge.io/api-1.0/libusb_api.html)