Fazer funcionar no Linux um dispositivo sem documentação
Um controle USB sem suporte oficial, um capturador de pacotes e um workspace de Rust. Como se ataca um protocolo que ninguém publicou.
Comprei um controle de botões pensado para streaming. Funcionava no Windows com o software do fabricante. No Linux, nada: o sistema o via como um dispositivo HID genérico e os botões não faziam absolutamente nada de útil.
Não havia documentação do protocolo. Não havia SDK. Fabricante não responde esse tipo de pergunta.
Acabei escrevendo o driver. Conto porque o método serve para qualquer integração em que a outra ponta é uma caixa-preta, e isso acontece bem mais do que parece: uma central telefônica antiga, uma leitora de cartões, uma balança industrial, um ERP sem API.
Primeiro olhar, depois escrever
A tentação é abrir o editor. É o erro.
A primeira coisa é capturar tráfego real. No Linux, o usbmon mostra tudo o que entra e sai de uma porta USB, e o Wireshark lê isso direto. Com o dispositivo funcionando numa máquina virtual com Windows, você aperta botões e observa quais pacotes saem.
O que você procura nessa montanha de bytes são padrões:
- Apertar o botão 1 sempre produz o mesmo byte na mesma posição.
- Soltá-lo produz outro.
- Apertar dois botões ao mesmo tempo produz a soma, o que te diz que é um mapa de bits e não um índice.
Esse último é o tipo de descoberta que muda todo o desenho do código. E não se deduz lendo: deduz-se apertando botões e anotando.
Anote tudo, inclusive o que você não entende
Na minha captura havia um byte que mudava sem que eu tocasse em nada. Perdi algumas horas tentando dar sentido a ele até entender que era um contador de sequência: subia sozinho, reiniciava em 255 e não significava mais nada.
Deixei isso escrito no repositório, em texto puro, ao lado do código. Não porque seja elegante, mas porque daqui a seis meses eu mesmo não vou lembrar, e porque outra pessoa vai querer entender por que esse byte é ignorado.
Um protocolo reconstruído sem anotações é um protocolo que vai ter que ser reconstruído de novo.
Por que Rust, e por que três crates
Escolhi Rust por um motivo concreto: num driver, os erros são de bytes e de tempo, e o compilador tira do seu caminho toda uma família de erros de memória enquanto você briga com os outros dois.
Dividi em três partes:
- O protocolo, sem nada de entrada e saída. Recebe bytes, devolve eventos. Dá para testar sem o dispositivo plugado.
- O transporte, que fala com o USB de verdade.
- A aplicação, que traduz eventos em ações do sistema.
Essa separação não é cerimônia. É o que permite ter testes de verdade: 90% da lógica é testada com bytes guardados num arquivo, sem hardware. Se tudo estivesse junto, testar qualquer coisa exigiria ter o aparelho plugado, e os testes nunca seriam escritos.
Teste onde você não escreveu
O driver roda em CI contra Fedora, Debian, Ubuntu e Arch. Não é excesso de zelo: as permissões de acesso ao USB e as regras do udev mudam entre distribuições, e é exatamente o tipo de coisa que funciona na sua máquina e falha na de quem instala.
Se você vai publicar algo que toca o sistema, teste em mais de um sistema. É barato e te poupa metade das issues.
O que fica
Quando um fornecedor diz “não tem API”, o que quase sempre quer dizer é “não tem API documentada”. Se existe um cliente que funciona, existe um protocolo, e um protocolo pode ser observado.
Nem sempre compensa: leva tempo, e se o fabricante mudar o firmware você recomeça. Mas quando a alternativa é trocar todo o parque de equipamentos, duas semanas de engenharia reversa saem bem mais baratas.
O código está publicado com licença MIT, caso o ponto de partida seja útil para alguém.