# 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)