← Tutti gli articoli

· 3 min di lettura

Far funzionare su Linux un dispositivo senza documentazione

Un controller USB senza supporto ufficiale, un analizzatore di pacchetti e un workspace Rust. Come si affronta un protocollo che nessuno ha pubblicato.

RustUSBLinux

Ho comprato un controller a pulsanti pensato per lo streaming. Su Windows funzionava con il software del produttore. Su Linux, niente: il sistema lo vedeva come un dispositivo HID generico e i pulsanti non facevano assolutamente nulla di utile.

Non c’era documentazione del protocollo. Non c’era SDK. I produttori a queste domande non rispondono.

Ho finito per scrivere il driver. Lo racconto perché il metodo vale per qualunque integrazione in cui dall’altra parte c’è una scatola nera, e succede molto più spesso di quanto sembri: una vecchia centrale telefonica, un lettore di tessere, una bilancia industriale, un ERP senza API.

Prima guardare, poi scrivere

La tentazione è aprire l’editor. È l’errore.

La prima cosa è catturare traffico vero. Su Linux, usbmon mostra tutto quello che entra ed esce da una porta USB, e Wireshark lo legge direttamente. Con il dispositivo in funzione su una macchina virtuale Windows, premi i pulsanti e osservi quali pacchetti escono.

Quello che cerchi in quella montagna di byte sono schemi ricorrenti:

Quest’ultimo è il tipo di scoperta che cambia tutto il disegno del codice. E non si deduce leggendo: si deduce premendo pulsanti e prendendo appunti.

Annota tutto, anche quello che non capisci

Nella mia cattura c’era un byte che cambiava senza che io toccassi nulla. Ho perso un paio d’ore a cercare di dargli un senso finché ho capito che era un contatore di sequenza: saliva da solo, ripartiva a 255 e non significava altro.

L’ho lasciato scritto nel repository, in testo semplice, accanto al codice. Non perché sia elegante, ma perché fra sei mesi io stesso non me lo ricorderò, e perché qualcun altro vorrà capire perché quel byte viene ignorato.

Un protocollo ricostruito senza appunti è un protocollo da ricostruire di nuovo.

Perché Rust, e perché tre crate

Ho scelto Rust per un motivo concreto: in un driver gli errori sono di byte e di tempi, e il compilatore ti toglie di mezzo tutta una famiglia di errori di memoria mentre combatti con gli altri due.

L’ho diviso in tre parti:

  1. Il protocollo, senza alcun ingresso o uscita. Riceve byte, restituisce eventi. Si testa senza avere il dispositivo collegato.
  2. Il trasporto, che parla con l’USB vero.
  3. L’applicazione, che traduce gli eventi in azioni di sistema.

Questa separazione non è cerimonia. È ciò che permette di avere test veri: il 90% della logica si prova con byte salvati in un file, senza hardware. Se fosse tutto insieme, provare qualsiasi cosa richiederebbe avere l’apparecchio collegato, e i test non si scriverebbero mai.

Provalo dove non l’hai scritto

Il driver gira in CI su Fedora, Debian, Ubuntu e Arch. Non è eccesso di zelo: i permessi di accesso all’USB e le regole di udev cambiano fra le distribuzioni, ed è esattamente il tipo di cosa che funziona sulla tua macchina e fallisce su quella di chi lo installa.

Se pubblichi qualcosa che tocca il sistema, provalo su più di un sistema. Costa poco e ti risparmia metà delle segnalazioni.

Cosa ci si porta a casa

Quando un fornitore ti dice “non ha API”, quello che quasi sempre intende è “non ha API documentate”. Se esiste un client che funziona, esiste un protocollo, e un protocollo si può osservare.

Non sempre conviene: richiede tempo, e se il produttore cambia il firmware si ricomincia. Ma quando l’alternativa è sostituire l’intero parco apparati, un paio di settimane di reverse engineering costano parecchio meno.

Il codice è pubblicato con licenza MIT, nel caso il punto di partenza serva a qualcuno.

Ti suona familiare? Raccontami il tuo caso

Frecce per muoverti, Invio per scegliere, Esc per chiudere