DEV Community

Emir Mrkaljević
Emir Mrkaljević

Posted on Edited on

Before you reverse engineer a USB device, check if someone already did

I built usb-rea to take a USB capture of a vendor app, infer the protocol, and write a pyusb driver. The earlier posts in this series are about that second half: the overall pipeline, handshakes, named readings, image streams, and what a real AIO capture found in my tests.

This post is about a step I should have automated earlier. Before you capture anything, it is worth asking whether an open-source project already speaks that device's protocol. Until v0.7.12 I still did that by hand, grepping liquidctl and rivalcfg for a VID:PID. smichea suggested in Discussion #3 that usb-rea should do the lookup itself. So it does now.

usb-rea lookup

pip install -U usb-rea
usb-rea lookup 1e71:170e
Enter fullscreen mode Exit fullscreen mode
1e71:170e  NZXT Kraken X (X42, X52, X62 or X72) (NZXT)
  protocol: documented
  - liquidctl: https://github.com/liquidctl/liquidctl/blob/main/liquidctl/driver/kraken2.py (fan/pump duty, lighting, status)
  - usb-rea validation: https://github.com/emirmrkaljevic/usb-rea/blob/main/docs/VALIDATION.md (fan duty command recovered byte for byte)
  note: A matching USB ID alone does not prove your unit speaks the same protocol; check the project's notes.
Enter fullscreen mode Exit fullscreen mode

That is the real output from 0.7.12, offline, no network involved. The ID is the NZXT Kraken X family. liquidctl already implements fan and pump duty, lighting and status in kraken2.py. usb-rea's own validation recovers that fan duty command byte for byte; the write-up is in docs/VALIDATION.md.

The note at the bottom is there on purpose. Vendors reuse IDs across revisions, and some IDs belong to a generic chip rather than one product. A matching ID is a hint to go read that project's notes, not proof that your unit speaks the same protocol.

What the offline table covers

The lookup is a curated table, not a search of every USB driver on GitHub. It covers the devices usb-rea is validated against, plus a few case studies:

  • liquidctl: NZXT Kraken X/Z family, and Corsair Hydro Platinum / Pro XT / Elite
  • rivalcfg: SteelSeries Rival 100 and 105
  • turing-smart-screen-python
  • linux-thermalright-lcd-control
  • usb-rea's own case studies (Corsair Hydro Platinum, ASUS TUF Gaming LC III LCD)

Vendor and product names also come from the system's usb.ids when it is installed. --usb-ids points at a file if you want to override that. --json prints the same result as JSON.

Here is a case where the hint needs a closer look. 1a86:5722 is a generic WCH serial bridge ID:

usb-rea lookup 1a86:5722
Enter fullscreen mode Exit fullscreen mode
1a86:5722  Turing Smart Screen 3.5" (rev A) / XuanFang (rev B) (QinHeng Electronics)
  protocol: documented
  - turing-smart-screen-python: https://github.com/mathoudebine/turing-smart-screen-python (serial protocol for revisions A and B)
  limits: generic WCH serial bridge ID; the project tells revisions apart by the USB serial number (USB35INCHIPSV2 is rev A), and unrelated devices use this ID too
  note: A matching USB ID alone does not prove your unit speaks the same protocol; check the project's notes.
Enter fullscreen mode Exit fullscreen mode

If your gadget only happens to use the same bridge chip, that project is not a driver for your device.

Look up every device in a capture

You can pass a pcap or pcapng instead of a VID:PID. usb-rea reads the IDs out of the device descriptors in the capture (the GET_DESCRIPTOR replies). I ran this on the trimmed ASUS TUF Gaming LC III capture that ships in the repo as tests/data/asus-tuf-lc3-lcd.pcap.gz. The command reads uncompressed pcap/pcapng, so I decompressed it first:

gunzip -c tests/data/asus-tuf-lc3-lcd.pcap.gz > asus-tuf-lc3-lcd.pcap
usb-rea lookup asus-tuf-lc3-lcd.pcap
Enter fullscreen mode Exit fullscreen mode
0b05:1c7b  ASUS TUF Gaming LC III LCD (ASUSTek Computer, Inc.)
  protocol: documented
  - usb-rea case study: https://github.com/emirmrkaljevic/usb-rea/blob/main/docs/case-study-asus-tuf-lc3.md (brightness command and JPEG stream from one real capture; not a full driver)
  note: A matching USB ID alone does not prove your unit speaks the same protocol; check the project's notes.
Enter fullscreen mode Exit fullscreen mode

That is also honest: I have a case study of the brightness command and the JPEG stream from one real capture, not a full open driver. If a capture has no descriptor (you started recording after the device was already plugged in), lookup says so and asks you to pass the VID:PID from lsusb, or to replug while capturing.

Optional: ask OpenDeviceDB

Offline is the default. Nothing leaves your machine. --online also queries OpenDeviceDB's read-only API and sends only the VID:PID, never the capture. If there is no network, lookup prints the error and keeps the offline answer.

usb-rea lookup 1e71:170e --online
Enter fullscreen mode Exit fullscreen mode
1e71:170e  NZXT Kraken X (X42, X52, X62 or X72) (NZXT)
  protocol: documented
  - liquidctl: https://github.com/liquidctl/liquidctl/blob/main/liquidctl/driver/kraken2.py (fan/pump duty, lighting, status)
  - usb-rea validation: https://github.com/emirmrkaljevic/usb-rea/blob/main/docs/VALIDATION.md (fan duty command recovered byte for byte)
  OpenDeviceDB: NZXT Kraken X2, controllable (openrgb): https://opendevicedb.org/devices/nzxt/kraken-x2
    - OpenRGB: https://gitlab.com/CalcProgrammer1/OpenRGB/-/tree/master/Controllers/NZXTKrakenController
  note: A matching USB ID alone does not prove your unit speaks the same protocol; check the project's notes.
Enter fullscreen mode Exit fullscreen mode

Same Kraken ID, plus a public OpenDeviceDB record that points at OpenRGB. An ID with nothing in the local table and nothing in OpenDeviceDB stays unknown. I tried a made-up NZXT product ID that is not in the table:

usb-rea lookup 1e71:9999 --online
Enter fullscreen mode Exit fullscreen mode
1e71:9999  NZXT
  protocol: unknown
  other devices from this vendor are handled by: liquidctl
  OpenDeviceDB: no record for this ID
Enter fullscreen mode Exit fullscreen mode

usb-rea still tells you that other NZXT devices are handled by liquidctl, which is often the useful next place to look. A fully unknown ID, with no vendor in the table either, is just unknown device.

The same lookup is available as the MCP tool lookup_device when you run usb-rea mcp. It takes a device argument (a VID:PID or a capture path) and an optional online flag, and returns JSON.

When nothing exists yet

That is the case the rest of usb-rea is for. The built-in demo uses a synthetic fan controller with a fake ID, 1234:abcd. lookup leaves it unknown, then the pipeline infers the protocol from the labelled capture and writes a driver anyway:

usb-rea lookup 1234:abcd
usb-rea demo --device fan
Enter fullscreen mode Exit fullscreen mode
1234:abcd  unknown device
  protocol: unknown
  nothing known offline; try --online (OpenDeviceDB) or capture it and find out
Enter fullscreen mode Exit fullscreen mode
captured 11 labelled actions, device 1234:abcd

mode         100%  0x5a | counter (+1 per message) | 0x30 | lookup 1→0x11, 2→0x22, 3→0x47 | 0x00 | 0x00 | u16le crc16_modbus(bytes[0:6])
fan          100%  0x5a | counter (+1 per message) | 0x10 | u16be round(value * 1 + 0) | 0x00 | u16le crc16_modbus(bytes[0:6])
color        100%  0x5a | counter (+1 per message) | 0x20 | round(value * 1 + 0) | round(value[1] * 1 + 0) | round(value[2] * 1 + 0) | u16le crc16_modbus(bytes[0:6])
  color only sent while mode 3 (3x; other commands ran in mode 1, 2): the driver sets it first

handshake (3 messages sent once when the app opens the device):
  1. 5a 00 01 00 00 00 0d 1d -> a5 01 ?? ?? ?? ?? ??, sent again 2x later (session reopened)
  2. 5a 00 02 38 b4 e6 52 1e 1e -> a5 02 ?? ??, bytes 3-6 = token from step 1, sent again 2x later (session reopened)
     reply byte 2 = 01 (2x, most common): the steps below
     reply byte 2 = 02 (1x): 5a 00 04 01 00 00 5c 11 -> a5 04 01 ??, 5a 00 03 01 00 00 5d 65 -> a5 03 01 ??
  3. 5a 00 03 01 00 00 5d 65 -> a5 03 01 ??, sent again 1x later (session reopened)

device replies:
mode         6/6  0xa5 | = query byte 2 (0x30) | = query byte 1 | = query byte 3 | 0x00 | sum8(bytes[0:5])
fan          6/6  0xa5 | = query byte 2 (0x10) | = query byte 1 | u16le round(value * 1 + 0) | sum8(bytes[0:5])
color        6/6  0xa5 | = query byte 2 (0x20) | = query byte 1 | 0x00 | 0x00 | sum8(bytes[0:5])
poll 1       7/7  0xa5 | = query byte 2 (0x40) | u16le rpm = raw, seen 800..4011 | u8 temp = raw, seen 35..38 | mode: 0x11 = silent, 0x22 = balanced, 0x47 = turbo | sum8(bytes[0:6])

24 of 24 'seen' observations matched a reply byte (mode, rpm, temp)

wrote usb-rea-demo/report.md, usb-rea-demo/fan_driver.py and the Wireshark dissector usb-rea-demo/fan.lua
Enter fullscreen mode Exit fullscreen mode

On a real Windows-only gadget the flow is: capture the vendor app with usbmon or USBPcap, mark the clicks (or pass --labels if you only have a pcap), then:

usb-rea report lamp.pcapng -o report.md
usb-rea scaffold lamp.pcapng -o lamp_driver.py
Enter fullscreen mode Exit fullscreen mode

If you have a device whose protocol is not in the lookup table, a capture is still the most useful thing you can send me. There is a device request thread. usb-rea is on GitHub and PyPI (v0.7.12).

I built usb-rea with help from AI coding tools; the inference itself is deterministic and every claim in its reports points at captured frames.

Top comments (0)