2 * Routines for the disassembly of the command protocol for the
3 * DJI Phantom 2 Vision+ UAV
4 * http://www.dji.com/product/phantom-2-vision-plus
7 * Copyright 2014,2015 Joerg Mayer (see AUTHORS file)
9 * Wireshark - Network traffic analyzer
10 * By Gerald Combs <gerald@wireshark.org>
11 * Copyright 1998 Gerald Combs
13 * SPDX-License-Identifier: GPL-2.0-or-later
20 #include <epan/packet.h>
21 /* TCP desegmentation */
22 #include "packet-tcp.h"
23 /* Request Response tracking */
24 #include <epan/conversation.h>
25 #include <epan/prefs.h>
27 void proto_register_djiuav(void);
28 void proto_reg_handoff_djiuav(void);
30 static dissector_handle_t djiuav_handle
;
32 /* Enable desegmentation of djiuav over TCP */
33 static bool djiuav_desegment
= true;
35 /* Command/Response tracking */
36 typedef struct _djiuav_conv_info_t
{
40 typedef struct _djiuav_transaction_t
{
43 uint32_t request_frame
;
45 nstime_t request_time
;
46 } djiuav_transaction_t
;
48 /* Finally: Protocol specific stuff */
50 /* protocol handles */
51 static int proto_djiuav
;
54 static int ett_djiuav
;
57 static int hf_djiuav_magic
;
58 static int hf_djiuav_length
;
59 static int hf_djiuav_flags
;
60 static int hf_djiuav_seqno
;
61 static int hf_djiuav_cmd
;
62 static int hf_djiuav_checksum
;
64 static int hf_djiuav_cmd04_unknown
;
65 static int hf_djiuav_resp04_unknown
;
67 static int hf_djiuav_cmd20_unknown
;
69 static int hf_djiuav_resp20_unknown
;
71 static int hf_djiuav_cmdunk
;
72 static int hf_djiuav_respunk
;
73 static int hf_djiuav_extradata
;
74 /* hf request/response tracking */
75 static int hf_djiuav_response_in
;
76 static int hf_djiuav_response_to
;
77 static int hf_djiuav_response_time
;
79 #define PROTO_SHORT_NAME "DJIUAV"
80 #define PROTO_LONG_NAME "DJI UAV Drone Control Protocol"
82 #define PORT_DJIUAV 2001 /* Not IANA registered */
84 static const value_string djiuav_pdu_type
[] = {
91 request_response_handling(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*djiuav_tree
,
94 conversation_t
*conversation
;
95 djiuav_conv_info_t
*djiuav_info
;
96 djiuav_transaction_t
*djiuav_trans
;
102 is_cmd
= (pinfo
->match_uint
== pinfo
->destport
);
103 seq_no
= tvb_get_letohs(tvb
, offset
+ 4);
104 packet_type
= tvb_get_uint8(tvb
, offset
+ 6);
106 conversation
= find_or_create_conversation(pinfo
);
107 djiuav_info
= (djiuav_conv_info_t
*)conversation_get_proto_data(conversation
, proto_djiuav
);
109 djiuav_info
= wmem_new(wmem_file_scope(), djiuav_conv_info_t
);
110 djiuav_info
->pdus
=wmem_map_new(wmem_file_scope(), g_direct_hash
, g_direct_equal
);
112 conversation_add_proto_data(conversation
, proto_djiuav
, djiuav_info
);
114 if (!pinfo
->fd
->visited
) {
116 djiuav_trans
=wmem_new(wmem_file_scope(), djiuav_transaction_t
);
117 djiuav_trans
->request_frame
=pinfo
->num
;
118 djiuav_trans
->reply_frame
=0;
119 djiuav_trans
->request_time
=pinfo
->abs_ts
;
120 djiuav_trans
->seqno
=seq_no
;
121 djiuav_trans
->command
=packet_type
;
122 wmem_map_insert(djiuav_info
->pdus
, GUINT_TO_POINTER((unsigned)seq_no
), (void *)djiuav_trans
);
124 djiuav_trans
=(djiuav_transaction_t
*)wmem_map_lookup(djiuav_info
->pdus
, GUINT_TO_POINTER((unsigned)seq_no
));
126 /* Special case: djiuav seems to send 0x24 replies with seqno 0 and without a request */
127 if (djiuav_trans
->reply_frame
== 0)
128 djiuav_trans
->reply_frame
=pinfo
->num
;
132 djiuav_trans
=(djiuav_transaction_t
*)wmem_map_lookup(djiuav_info
->pdus
, GUINT_TO_POINTER((unsigned)seq_no
));
135 /* djiuav_trans may be 0 in case it's a reply without a matching request */
137 if (djiuav_tree
&& djiuav_trans
) {
139 if (djiuav_trans
->reply_frame
) {
142 it
= proto_tree_add_uint(djiuav_tree
, hf_djiuav_response_in
,
143 tvb
, 0, 0, djiuav_trans
->reply_frame
);
144 proto_item_set_generated(it
);
147 if (djiuav_trans
->request_frame
) {
151 it
= proto_tree_add_uint(djiuav_tree
, hf_djiuav_response_to
,
152 tvb
, 0, 0, djiuav_trans
->request_frame
);
153 proto_item_set_generated(it
);
155 nstime_delta(&ns
, &pinfo
->abs_ts
, &djiuav_trans
->request_time
);
156 it
= proto_tree_add_time(djiuav_tree
, hf_djiuav_response_time
, tvb
, 0, 0, &ns
);
157 proto_item_set_generated(it
);
164 dissect_djiuav_pdu(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
)
167 proto_tree
*djiuav_tree
= NULL
;
173 is_cmd
= (pinfo
->match_uint
== pinfo
->destport
);
174 packet_type
= tvb_get_uint8(tvb
, 6);
176 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, PROTO_SHORT_NAME
);
177 col_add_str(pinfo
->cinfo
, COL_INFO
, is_cmd
?"C: ":"R: ");
178 col_append_str(pinfo
->cinfo
, COL_INFO
, val_to_str(packet_type
,
179 djiuav_pdu_type
, "Type 0x%02x"));
181 ti
= proto_tree_add_item(tree
, proto_djiuav
, tvb
, offset
, -1, ENC_NA
);
182 djiuav_tree
= proto_item_add_subtree(ti
, ett_djiuav
);
184 request_response_handling(tvb
, pinfo
, djiuav_tree
, offset
);
187 proto_tree_add_item(djiuav_tree
, hf_djiuav_magic
, tvb
, offset
, 2,
191 pdu_length
= tvb_get_uint8(tvb
, offset
);
192 proto_tree_add_item(djiuav_tree
, hf_djiuav_length
, tvb
, offset
, 1,
196 proto_tree_add_item(djiuav_tree
, hf_djiuav_flags
, tvb
, offset
, 1,
200 proto_tree_add_item(djiuav_tree
, hf_djiuav_seqno
, tvb
, offset
, 2,
204 proto_tree_add_item(djiuav_tree
, hf_djiuav_cmd
, tvb
, offset
, 1,
208 if (is_cmd
) { /* Command */
209 switch (packet_type
) {
210 case 0x20: /* Set time */
211 /* FIXME: Properly decode this: year(lo) year(hi) month date hour minute second */
212 proto_tree_add_item(djiuav_tree
, hf_djiuav_cmd20_unknown
, tvb
, offset
, 7,
217 proto_tree_add_item(djiuav_tree
, hf_djiuav_cmdunk
, tvb
, offset
, pdu_length
- 8,
219 offset
+= (pdu_length
- 8);
222 } else { /* Response */
223 switch (packet_type
) {
225 proto_tree_add_item(djiuav_tree
, hf_djiuav_respunk
, tvb
,
226 offset
, pdu_length
- 8, ENC_NA
);
227 offset
+= (pdu_length
- 8);
231 if (offset
< pdu_length
- 1) { /* We guessed wrong about the cmd len */
232 proto_tree_add_item(djiuav_tree
, hf_djiuav_extradata
, tvb
, offset
,
233 pdu_length
- 1 - offset
, ENC_NA
);
234 offset
+= pdu_length
- 1 - offset
;
236 /* FIXME: calculate XOR and validate transmitted value */
237 proto_tree_add_checksum(djiuav_tree
, tvb
, offset
, hf_djiuav_checksum
, -1, NULL
, pinfo
, 0, ENC_BIG_ENDIAN
, PROTO_CHECKSUM_NO_FLAGS
);
245 test_djiuav(tvbuff_t
*tvb
)
247 /* Minimum of 8 bytes, beginning with magic bytes 0x55BB */
248 if ( tvb_captured_length(tvb
) < 8 /* Size of a command with empty data is at least 8 */
249 || tvb_get_ntohs(tvb
, 0) != 0x55BB
256 /* Get the length of the full pdu */
258 get_djiuav_pdu_len(packet_info
*pinfo _U_
, tvbuff_t
*tvb
, int offset
, void *data _U_
)
260 return tvb_get_uint8(tvb
, offset
+ 2);
264 dissect_djiuav_static(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
266 if ( !test_djiuav(tvb
) ) {
269 tcp_dissect_pdus(tvb
, pinfo
, tree
, djiuav_desegment
, 8,
270 get_djiuav_pdu_len
, dissect_djiuav_pdu
, data
);
272 return tvb_captured_length(tvb
);
276 proto_register_djiuav(void)
278 static hf_register_info hf
[] = {
282 { "Protocol Magic", "djiuav.magic", FT_UINT16
, BASE_HEX
, NULL
,
286 { "PDU Length", "djiuav.length", FT_UINT8
, BASE_HEX
, NULL
,
290 { "Flags", "djiuav.flags", FT_UINT8
, BASE_HEX
, NULL
,
294 { "Sequence No", "djiuav.seqno", FT_UINT16
, BASE_DEC
, NULL
,
298 { "PDU Type", "djiuav.pdutype", FT_UINT8
, BASE_HEX
, VALS(djiuav_pdu_type
),
301 { &hf_djiuav_checksum
,
302 { "Checksum", "djiuav.checksum", FT_UINT8
, BASE_HEX
, NULL
,
307 { &hf_djiuav_cmd04_unknown
,
308 { "C04 Unknown", "djiuav.cmd04.unknown", FT_UINT8
, BASE_HEX
, NULL
,
311 { &hf_djiuav_resp04_unknown
,
312 { "R04 Unknown", "djiuav.resp04.unknown", FT_UINT8
, BASE_HEX
, NULL
,
316 { &hf_djiuav_cmd20_unknown
,
317 { "Time in BCD", "djiuav.cmd04.bcdtime", FT_BYTES
, BASE_NONE
, NULL
,
320 { &hf_djiuav_resp20_unknown
,
321 { "R20 Unknown", "djiuav.resp04.unknown", FT_UINT8
, BASE_HEX
, NULL
,
326 { "C Unknown", "djiuav.cmd.unknown", FT_BYTES
, BASE_NONE
, NULL
,
330 { &hf_djiuav_respunk
,
331 { "R Unknown", "djiuav.resp.unknown", FT_BYTES
, BASE_NONE
, NULL
,
334 /* Extra Data (unexpected) */
335 { &hf_djiuav_extradata
,
336 { "Unexpected", "djiuav.unexpected", FT_BYTES
, BASE_NONE
, NULL
,
339 /* Request - Response tracking */
340 { &hf_djiuav_response_in
,
341 { "Response In", "djiuav.response_in", FT_FRAMENUM
, BASE_NONE
, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE
),
342 0x0, "Matching response in frame", HFILL
}},
344 { &hf_djiuav_response_to
,
345 { "Request In", "djiuav.response_to",
346 FT_FRAMENUM
, BASE_NONE
, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST
),
347 0x0, "Matching command in frame", HFILL
}},
349 { &hf_djiuav_response_time
,
350 { "Response Time", "djiuav.response_time",
351 FT_RELATIVE_TIME
, BASE_NONE
, NULL
,
352 0x0, "Time between Command and matching Response", HFILL
}},
354 static int *ett
[] = {
357 module_t
*djiuav_module
;
359 proto_djiuav
= proto_register_protocol(PROTO_LONG_NAME
, PROTO_SHORT_NAME
, "djiuav");
360 proto_register_field_array(proto_djiuav
, hf
, array_length(hf
));
361 proto_register_subtree_array(ett
, array_length(ett
));
364 djiuav_module
= prefs_register_protocol(proto_djiuav
, NULL
);
366 prefs_register_bool_preference(djiuav_module
, "desegment",
367 "Reassemble DJIUAV messages",
368 "Whether DJIUAV should reassemble messages spanning multiple"
369 " TCP segments (required to get useful results)",
372 djiuav_handle
= register_dissector("djiuav", dissect_djiuav_static
, proto_djiuav
);
376 proto_reg_handoff_djiuav(void)
378 dissector_add_uint_with_preference("tcp.port", PORT_DJIUAV
, djiuav_handle
);
382 * Editor modelines - https://www.wireshark.org/tools/modelines.html
387 * indent-tabs-mode: t
390 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
391 * :indentSize=8:tabSize=8:noTabs=false: