HID Handshake
Before the Host and the Controller can exchange any meaningful data,
they have to carry out a handshake. PioneerDJ implemented a simple
Challenge-Response-Protocol
to authenticate the DJ software and the controller (presumably to be able to
charge licensing fees from 3rd party DJing software for being compatible with
the CDJ). This is accomplished via a DLL called libpcon.dll
which are included
"Native Instruments TraktorDJ 2" and "SeratoDJ Pro" installations. The sole purpose
of that DLL is to implement the logic required to carry out a successful handshake.
The handshake is initiated via the
CDJ by using the Link menu: .
This will cause the CDJ to start sending USB-MIDI packets and the first
Packet of the handshake procedure:
USB-Devices are by definition unable to send data to the host on their own. The host instead has to constantly pull for changes. So make sure the Software running on the host is doing that before connecting initiating the connection on the CDJ. |
This also mostly applies to other Pioneer devices than the CDJ, notably pioneer devices that only have MIDI+Sysex controls instead of HID, but are still locked without performing this handshake first (via Sysex). |
Serialization Specifics
in the following documentation, buffers and integer values are used interchangeably. All integers larger than one byte are always (de-)serialized in big-endian order. |
The handshake packets exchanged over HID always have the following structure:
Since the payload is of variable length and can exceed the length limit, the packet begins with an extended header. This is followed by the first TLV (which will be referred to as "super-tlv" for the rest of this doc) which contains a list of arguments. The "T" of the super-tlv indicates the current stage in the handshake procedure. The "L" of the super-tlv indicates the length of the entire payload including the TLV header (so for a payload with 9 Bytes length, the length of the TLV will be 9+2=11).
if messages are exchanged over sysex messages, the packet header is instead of the Sysex Extended Header form. |
TLV Type | Description | Direction |
---|---|---|
|
CDJ announces itself to the host |
CDJ→Host only |
|
Host start the handshake by sending the CDJ the challenge |
Host→CDJ only |
|
CDJ responds with response for the hosts challenge and its own challenge |
CDJ→Host only |
|
Host responds with response to CDJs challenge and a vendor-specific device ID |
Host→CDJ only |
|
CDJ confirms that a successful handshake was established |
CDJ→Host only |
Each argument is another TLV and follows the same rules as the super TLV. However, its type value indicates the purpose of the data transmitted, but the meaning of the payload changes based on the direction of transfer. It seems like the CDJ accepts the arguments in arbitrary order, but the following figures will reflect the ordering in which they were observed in the wild.
Argument Type | Description | Notes |
---|---|---|
|
Manufacturer-name |
ASCII encoded |
|
Product-name |
ASCII encoded |
|
CDJ authentication |
Buffer spread out |
|
Host authentication |
Buffer spread out |
|
Vendor-specific device ID |
Buffer spread out |
For compatibility reasons with sysex, the buffers in the arguments
0x03 , 0x04 and 0x05 are spread out. Meaning each byte is split in half and
serialized into two bytes. So the buffer [0x12,0x34] will actually be
transmitted as [0x01, 0x02, 0x03, 0x04] . This is because you can only send
7-bit bytes via sysex, which is fine for transmitting Ascii-text, but not for
binary data containing 8-bit data. When describing how the arguments will be
formed, the compact version will always be assumed while the bytefield figures
will contain the sizes of the spread out buffer as they are transmitted.
|
Handshake procedure
The CDJ initiates the connection by sending a super tlv of type 0x11
and a single nested tlv of type 0x01
which contain the major and minor
version of the CDJs firmware. At the time of writing, the newest
version is 1.85
.
At this point the host has to respond with the following message:
After the host has sent its challenge packet, the CDJ will respond with the following:
Which is then again followed up by a packet from the host.
The Handshake ends with an acknowledgment from the CDJ:
In case anything goes wrong during the authentication steps, the
CDJ will send a reset packet before starting the entire handshake from the
beginning again. A reset packet contains no data and instead just all 0
:
"Cryptography"
This section will describe in detail how the different transmitted parameters
are generated. The authentication follows the simplest pattern of a mutual
challenge-response authentication scheme: One party provides a challenge which
the other party combines with a shared secret. This combined challenge is then
hashed to form the actual response.
The shared secret depends on the manufacturer of the DJ software trying to
communicate with the device in question. The secret is formed by XORing SeedE
with the manufacturer-dependent value (which are hardcoded into the CDJs firmware):
Manufacturer | Value |
---|---|
PioneerDJ |
0x680131FB |
Serato |
0x0D6F55AB |
NativeInstruments |
0x8C5B3F5D |
Atomix |
0x97779123 |
Algoriddim |
0x384B522B |
Mixvibes |
0x9EA1A8B6 |
Numark |
0x889127C7 |
Dolby |
0x7A2B59AB |
Generating SeedA
The exact way seedA is generated is currently unknown. However, I was able to identify a pattern which is present when posing as seratoDJ or Traktor Pro 2. It seems like the algorithm generates two ushorts (16bit unsigned integers) and then concats them after serializing them as uints (32bit). So the resulting buffer could look like this (before being spaced out for actual transmission):
Hashing
The CDJ uses a Fowler-Noll-Vo hash function (FNV-1a).
The value 0x811c9dc5
is used as the algorithms 32-bit FNV offset basis
while 0x1000193
serves as its FNV Prime
.
You can read more about FNV hashes on wikipedia
Here is a complete reference implementation in python3.8:
def FNV(buff: bytes) -> int:
hash = 0x811c9dc5
for val in buff:
hash = ((val^hash) * 0x1000193) & 0xFFFFFFFF
return hash
Validating the CDJs response
The CDJs response can be validated by simply calculating appending the common
secret and SeedA
to each other and hashing that: hashAd = hash(seedA+secret)
If hashAd
matches hashA
, the CDJs response is valid.
Building the response for the CDJ (HashE
)
Building HashE
is just as simple as validating the CDJs response:
hashE = hash(seedE+secret)
Building the vendor-specific device ID
The vendor-specific is static and can be taken from the following table:
Contributions Wanted: If you happen to have access to software from any of these manufacturers or hardware that is not yet registered, feel free to contact me by opening an issue on this projects github repository |
NativeInstruments | Serato | PioneerDJ | Atomix | Algoriddim | Mixvibes | Numark | Dolby | |
---|---|---|---|---|---|---|---|---|
CDJ2000NXS2 |
|
|
|
— |
— |
— |
— |
— |
DJM900NXS2 |
|
|
|
— |
— |
— |
— |
— |
CDJTOUR1 |
|
— |
— |
— |
— |
— |
— |
— |
DJMTOUR1 |
|
— |
— |
— |
— |
— |
— |
— |
XDJ1000MK2 |
|
— |
— |
— |
— |
— |
— |
— |
DJM450 |
|
— |
— |
— |
— |
— |
— |
— |
DJM250MK2 |
|
— |
— |
— |
— |
— |
— |
— |
DJM750MK2 |
|
— |
|
— |
— |
— |
— |
— |
DJMV10 |
|
|
— |
— |
— |
— |
— |
— |
XDJXZ |
Not supported |
|
Captures lead me to believe that there actually is no handshake taking place in this configuration |
— |
— |
— |
— |
— |
From my experiments, it looks like Atomix VirtualDJ actually poses as Rekordbox instead of using its own identifiers. |
Keepalive
Once a successful handshake occurred, the host has to keep the connection alive by sending packets at least once per second. However, it is recommended to send a packet at least every 400ms since that has been observed in tests with commercial DJing software. In order to keep the connection alive in cases where the host has no new information to update the CDJ with, the CDJ also accepts empty packets: