Ein undokumentiertes Gerät unter Linux zum Laufen bringen
Ein USB-Controller ohne offizielle Unterstützung, ein Paketmitschnitt und ein Rust-Workspace. Wie man ein Protokoll angeht, das niemand veröffentlicht hat.
Ich habe mir einen Tastencontroller fürs Streaming gekauft. Unter Windows lief er mit der Software des Herstellers. Unter Linux: nichts. Das System sah ihn als generisches HID-Gerät, und die Tasten taten überhaupt nichts Brauchbares.
Es gab keine Protokolldokumentation. Kein SDK. Hersteller beantworten solche Fragen nicht.
Am Ende habe ich den Treiber geschrieben. Ich erzähle es, weil die Methode für jede Integration taugt, bei der die Gegenseite eine Blackbox ist — und das kommt viel häufiger vor, als es klingt: eine alte Telefonanlage, ein Kartenleser, eine Industriewaage, ein ERP ohne API.
Erst schauen, dann schreiben
Die Versuchung ist, den Editor zu öffnen. Das ist der Fehler.
Als Erstes fängt man echten Verkehr mit. Unter Linux zeigt usbmon alles, was an einem USB-Port ein- und ausgeht, und Wireshark liest das direkt. Mit dem Gerät in einer Windows-VM drückt man Tasten und schaut, welche Pakete herauskommen.
Was man in diesem Berg von Bytes sucht, sind Muster:
- Taste 1 drücken erzeugt immer dasselbe Byte an derselben Stelle.
- Loslassen erzeugt ein anderes.
- Zwei Tasten gleichzeitig erzeugen die Summe — was Ihnen sagt, dass es eine Bitmaske ist und kein Index.
Das Letzte ist die Art Erkenntnis, die den ganzen Aufbau des Codes verändert. Und sie ergibt sich nicht aus dem Lesen, sondern aus dem Tastendrücken und Mitschreiben.
Schreiben Sie alles auf, auch was Sie nicht verstehen
In meinem Mitschnitt gab es ein Byte, das sich änderte, ohne dass ich etwas tat. Ich habe ein paar Stunden damit verloren, bis ich begriff, dass es ein Sequenzzähler war: Er lief von allein hoch, sprang bei 255 um und bedeutete sonst nichts.
Das habe ich im Repository festgehalten, als einfachen Text, direkt neben dem Code. Nicht weil es elegant ist, sondern weil ich mich in sechs Monaten selbst nicht erinnern werde — und weil jemand anderes wissen will, warum dieses Byte ignoriert wird.
Ein rekonstruiertes Protokoll ohne Notizen ist ein Protokoll, das man noch einmal rekonstruieren muss.
Warum Rust, und warum drei Crates
Ich habe Rust aus einem konkreten Grund gewählt: In einem Treiber geht es bei Fehlern um Bytes und um Zeit, und der Compiler nimmt Ihnen eine ganze Familie von Speicherfehlern ab, während Sie mit den anderen beiden ringen.
Ich habe es in drei Teile zerlegt:
- Das Protokoll, ganz ohne Ein- und Ausgabe. Es bekommt Bytes und liefert Ereignisse. Es lässt sich ohne angestecktes Gerät testen.
- Den Transport, der mit dem echten USB spricht.
- Die Anwendung, die Ereignisse in Systemaktionen übersetzt.
Diese Trennung ist keine Förmlichkeit. Sie ist es, was echte Tests möglich macht: 90 % der Logik wird mit Bytes aus einer Datei geprüft, ohne Hardware. Wäre alles ein Stück, bräuchte jeder Test das angesteckte Gerät — und die Tests würden nie geschrieben.
Testen Sie dort, wo Sie es nicht geschrieben haben
Der Treiber läuft in CI gegen Fedora, Debian, Ubuntu und Arch. Das ist kein übertriebener Eifer: Zugriffsrechte auf USB und udev-Regeln unterscheiden sich zwischen Distributionen, und das ist genau die Sorte Sache, die auf Ihrer Maschine läuft und auf der des Installierenden scheitert.
Wenn Sie etwas veröffentlichen, das ins System greift, testen Sie es auf mehr als einem System. Das kostet wenig und erspart Ihnen die Hälfte der Meldungen.
Was bleibt
Wenn ein Anbieter sagt “es hat keine API”, meint er fast immer “es hat keine dokumentierte API”. Wenn es einen funktionierenden Client gibt, gibt es ein Protokoll, und ein Protokoll lässt sich beobachten.
Es lohnt sich nicht immer: Es kostet Zeit, und wenn der Hersteller die Firmware ändert, fangen Sie von vorn an. Aber wenn die Alternative ist, den gesamten Gerätebestand zu tauschen, kommen zwei Wochen Reverse Engineering deutlich günstiger.
Der Code ist unter MIT-Lizenz veröffentlicht, falls jemandem der Ausgangspunkt nützt.