· 3 min de lectura
El checksum que no era CRC: cómo decodifiqué el protocolo del JamJum JP-1
Bitácora de ingeniería inversa de un controlador MIDI: capturas USB, una hipótesis equivocada y la pieza que faltaba para que el aparato aceptara color arbitrario por pad.
El JamJum JP-1 es un controlador MIDI de 16 pads RGB, 8 knobs y 6 botones de transporte. Funciona bien, pero su app oficial es limitada y el fabricante no publica el protocolo. Quería editarlo en lote, con mis propios efectos de luz, desde el navegador. Para eso había que aprender a hablarle.
Paso 1: escuchar la conversación
El aparato es USB-MIDI estándar, así que el tráfico se puede capturar con USBPcap y leer en Wireshark. La app oficial manda mensajes SysEx propietarios con esta forma:
F0 78 32 <comando de 3 bytes> <contenido> F7El contenido no son bytes «normales». Es un flujo de bits de 7 bits por byte, LSB primero: de cada byte se toman sus 7 bits bajos y se concatenan. Por eso un campo puede empezar a la mitad de un byte y terminar en el siguiente. Antes de entender cualquier valor, hubo que escribir un decodificador de ese flujo.
Paso 2: cambiar una sola cosa a la vez
El método que funcionó fue el más aburrido: cambiar un parámetro en la app oficial, capturar, y comparar bit a bit contra la captura anterior. Así salió el mapa de cada pad: canal, tipo (nota, CC, programa), número y rango de velocidad, en registros de 32 bits.
Y en cada bloque, al final, dos bytes que cambiaban «sin razón»: el checksum.
Paso 3: la hipótesis equivocada
Un checksum de dos bytes que cambia con todo suena a CRC. Probé si el sistema era lineal sobre GF(2) —como un CRC— y la prueba dijo que sí. El problema: tenía muy pocas ecuaciones para tantas incógnitas, y con pocos datos casi cualquier modelo «encaja». La hipótesis era falsa y me costó varias pruebas en el aparato descubrirlo.
La lección quedó anotada en la bitácora del proyecto: una prueba de solvencia infradeterminada no demuestra nada.
Paso 4: sumas modulares
Al replantearlo como una suma modular ponderada, todo empezó a cerrar. El de los pads, por ejemplo:
def checksum_pads(pads: list[dict]) -> tuple[int, int]: s = sum(p["canal"] + 16 * p["tipo"] + p["nota"] + p["min"] + p["max"] for p in pads) c = (510 - 2 * s) % 512 return c & 0x7F, (c >> 7) & 0x7F # bytes 76 y 77Lo verifiqué contra los 63 bloques oficiales que tenía capturados: idénticos byte por byte. Luego cayeron los de botones (8175 − 32·Σ) mod 8192 y knobs (15456 − 32·Σ) mod 16384. Misma familia.
Paso 5: el color se resistió
Con el color pasó algo engañoso. Un modelo «uniforme» encajaba perfecto en mis primeras capturas… porque esas capturas no tenían suficiente variedad: el modelo encajaba por alias, no porque fuera correcto. En el aparato, fallaba.
La salida fue diseñar una captura nueva, pensada para romper el modelo: los 16 pads con colores independientes, brillos variados e índices distintos. Con esos datos, una búsqueda de la forma (K + w·Σ) mod M sobre varias definiciones de Σ encontró la respuesta exacta:
def checksum_color(canales: list[int], indice_pad: int) -> tuple[int, int]: """canales: los 48 valores de 8 bits (16 pads × G, R, B).""" s = sum(canales) + indice_pad # ← la pieza que faltaba chk = (2720 - 32 * s) % 8192 return chk & 0x7F, (chk >> 7) & 0x7F # bytes 64 y 65La pieza que faltaba era sumar el índice del pad. Sin él, dos bloques con los mismos colores pero distinto índice daban el mismo resultado, y eso era imposible de conciliar con las capturas.
Resultado: 384 de 384 bloques reproducidos byte por byte, y un arcoíris arbitrario —16 colores distintos generados por mi código— aceptado por el aparato.
Lo que me llevo
- Diseña la captura que puede tumbar tu hipótesis, no la que la confirma.
- Verifica en el hardware, no solo en la computadora. El aparato es el único juez.
- Prueba la capa que usa la persona. Validé el motor desde la consola y la interfaz tenía otro error; ahora pruebo de punta a punta.
Hoy la app (JamJum Studio, en Web MIDI) lee la configuración al conectar, edita pads, knobs, botones y color, y suma efectos de luz que la app oficial no tiene. El caso completo está en proyectos.