DJI Protocol – Day 8 – Progress Report

It is official now – After weeks of full-time investigating the Dji Low-Level Wifi Protocol I had my first dream featuring HEX numbers. I had an operation years ago and within the dream I did explain my pain on a HEX scale to the doctor, whereas 0xFF represented severe pain and 0x00 no pain. Well,… seems like I committed myself to this project, maybe a little bit too much. However, despite the dream, there is other news as well. The OG’s packet descriptors slingshotted us miles ahead, such that we decided to focus on the Drone to Operator packets, as most Operator packets have been appropriately reasoned. With that said, we applied the same DUML packet extraction logic to the Drone’s packets and did receive mostly valid commands. (See Fig. 4) Some packets, however, had a valid CRC, but an unknown command-set. Moreover, a Drone packet may not only contain one, but several command-sets. Our current DUML extraction logic looks like following:

for (int idx = 0; idx < networkPacketData.Length; idx++)
     if (networkPacketData[idx] != 0x55) continue;

While Operator to Drone packets only contain one command, this snippet works like a charm. But as Drone to Operator packets contain several packets, this behaviour yields several issues:

  1. Is the 0x55 really a DUML delimiter, or just the contents of the Dji Wifi-Header? (See List. 1)
  2. Drone packets may span several UDP packets
  3. Custom logic to ‘strip out’ already extracted commands required
  4. What if we strip out a command which isn’t a command (E.g.: False positive DUML packet found)

In other words, we require an approach to extract DUML packets in a deterministic and predictable way. As the Wifi-Header of each packet has a dynamic bit-length, the first command’s position must be somehow hidden within those bits. But, before we present our findings, let’s have a look at the first byte of the Wifi-Header. (See Fig. 1)

Fig. 1.: Wifi-Header + Commands

Our assumption was, that the first byte of the packet indicates the packet length. That was correct for operator-to-drone packets, but isn’t valid for drone-to-operator packets. The packet has a length of 1472 bytes, which is 0x5C0 in HEX. However, what we got is 0xC0. Logically, we can’t write 12 bits into 8 bits, thus the information is hidden somewhere else. Let’s have a look at the second byte: 0x85. The second byte was throughout all operator-to-drone packets 0x80, and therefore, we did assume that we deal with the protocol version. But, oh boy, we were wrong. As all packets from operator-to-drone where smaller than 0xFF, there was no need to stuff more information into a second byte. But it turns out, if we apply following logic: ((0x01 & 0x0F) << 8) + 0x00, we do receive the correct HEX value 0x5C0. First mystery solved. This enables us to read just enough bytes from the stream, without interfering with other packet’s hidden DUML definitions.

The size of a packet – Check; but we still don’t know where the Wifi-Header ends. As we assume that the information is hidden within the Wifi-Header, we simply extract the first valid command (See List. 1) and trim out all DUML related bytes. What is left is the Wifi-Header. The only thing left to do is to compare different sized Wifi-Headers and to search for byte values representing its length. Our findings (See List. 2.) concludes that such an indicator byte is present at the static position 0x06.

Indicator @ 0x06SidePurposeDUML OffSet
0x00DroneUnknownNo DUML
0x01DroneCommand(0x1C << 1) + 0x20
0x04*OperatorCommand(0x0C << 1) + 0x1E
0x05OperatorCommand 0x14
0x06*OperatorCommand(0x0C << 1) + 0x1E
List. 2.: DUML OffSet guessing

*… May contain, based on another indicator, no DUML

byte offSet = 0x00;
byte pos = 0x00;

switch ((WhType)data[0x06])
    case WhType.DroneCmd1:
        offSet = (byte)(data[0x1C] << 1);
        pos = 0x20;
    case WhType.DroneImgFrame:
    case WhType.DroneCmd2:
    case WhType.OperatorCmd2:
        pos = 0x14;
    case WhType.OperatorCmd1:
    case WhType.OperatorCmd3:
        offSet = (byte)(data[0x0C] << 1);
        pos = 0x1E;
    case WhType.OperatorEmpty:
        pos = (byte)data.Length;

    default: throw new ArgumentException($"The provided data {data[0x06].ToHexString()} features an invalid {nameof(WhType)}");

return (byte)(pos + offSet);

Camera Footage

As a software-architect I am used receiving requirements of the software product up-front, in order to design a modular, maintainable and extendable architecture before writing a single line of code. However, in this case, the requirements are too vague in order make any future-proof decision and therefore, our software (available on GitHub) requires architectural adaptation from time to time, as we do obtain new knowledge day by day. With that said, we will refactor the current code and integrate the new findings as soon as possible, in order to switch our focus back to the drone-to-operator communication reverse-engineering. Nevertheless, that didn’t stop us from extracting our first images from the drone’s camera footage. Our miserable approach is to export all received bytes into a binary file and asking ffmpeg to turn it into something human viewable. We didn’t want to withhold our first results, so here it is:

Fig. 2.: ffmpeg recovery result

Enhanced Camera Footage

After refactoring and implementing (See List. 2. and List. 3.) appropriate Wifi-Headers, we are able to identify the drone’s camera frame updates in a deterministic way. (0x06 = 0x02) Hence, the overall quality and stability of the extracted camera feed is now as perfect as it can get (See Fig. 3.). The application has been extended to facilitate UDP-Packet to *.AVI conversion and is also capable of playing the camera feed within the application itself.

5 2 votes
Article Rating
Notify of
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

[…] Next up: DJI Protocol – Day 8 – Progress Report […]

[…] DUML-Type – The DUML’s content type. For more details see List. 2. […]