summaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-dji-uav.c
diff options
context:
space:
mode:
Diffstat (limited to 'epan/dissectors/packet-dji-uav.c')
-rw-r--r--epan/dissectors/packet-dji-uav.c392
1 files changed, 392 insertions, 0 deletions
diff --git a/epan/dissectors/packet-dji-uav.c b/epan/dissectors/packet-dji-uav.c
new file mode 100644
index 00000000..9788ed9b
--- /dev/null
+++ b/epan/dissectors/packet-dji-uav.c
@@ -0,0 +1,392 @@
+/* packet-dji-uav.c
+ * Routines for the disassembly of the command protocol for the
+ * DJI Phantom 2 Vision+ UAV
+ * http://www.dji.com/product/phantom-2-vision-plus
+ * and possibly others.
+ *
+ * Copyright 2014,2015 Joerg Mayer (see AUTHORS file)
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ */
+#include "config.h"
+
+#include <epan/packet.h>
+/* TCP desegmentation */
+#include "packet-tcp.h"
+/* Request Response tracking */
+#include <epan/conversation.h>
+#include <epan/prefs.h>
+
+void proto_register_djiuav(void);
+void proto_reg_handoff_djiuav(void);
+
+static dissector_handle_t djiuav_handle;
+
+/* Enable desegmentation of djiuav over TCP */
+static gboolean djiuav_desegment = TRUE;
+
+/* Command/Response tracking */
+typedef struct _djiuav_conv_info_t {
+ wmem_map_t *pdus;
+} djiuav_conv_info_t;
+
+typedef struct _djiuav_transaction_t {
+ guint16 seqno;
+ guint8 command;
+ guint32 request_frame;
+ guint32 reply_frame;
+ nstime_t request_time;
+} djiuav_transaction_t;
+
+/* Finally: Protocol specific stuff */
+
+/* protocol handles */
+static int proto_djiuav = -1;
+
+/* ett handles */
+static int ett_djiuav = -1;
+
+/* hf elements */
+static int hf_djiuav_magic = -1;
+static int hf_djiuav_length = -1;
+static int hf_djiuav_flags = -1;
+static int hf_djiuav_seqno = -1;
+static int hf_djiuav_cmd = -1;
+static int hf_djiuav_checksum = -1;
+#if 0
+static int hf_djiuav_cmd04_unknown = -1;
+static int hf_djiuav_resp04_unknown = -1;
+#endif
+static int hf_djiuav_cmd20_unknown = -1;
+#if 0
+static int hf_djiuav_resp20_unknown = -1;
+#endif
+static int hf_djiuav_cmdunk = -1;
+static int hf_djiuav_respunk = -1;
+static int hf_djiuav_extradata = -1;
+/* hf request/response tracking */
+static int hf_djiuav_response_in = -1;
+static int hf_djiuav_response_to = -1;
+static int hf_djiuav_response_time = -1;
+
+#define PROTO_SHORT_NAME "DJIUAV"
+#define PROTO_LONG_NAME "DJI UAV Drone Control Protocol"
+
+#define PORT_DJIUAV 2001 /* Not IANA registered */
+
+static const value_string djiuav_pdu_type[] = {
+ { 0x20, "Set Time" },
+
+ { 0, NULL }
+};
+
+static void
+request_response_handling(tvbuff_t *tvb, packet_info *pinfo, proto_tree *djiuav_tree,
+ guint32 offset)
+{
+ conversation_t *conversation;
+ djiuav_conv_info_t *djiuav_info;
+ djiuav_transaction_t *djiuav_trans;
+
+ guint16 seq_no;
+ gboolean is_cmd;
+ guint8 packet_type;
+
+ is_cmd = (pinfo->match_uint == pinfo->destport);
+ seq_no = tvb_get_letohs(tvb, offset + 4);
+ packet_type = tvb_get_guint8(tvb, offset + 6);
+
+ conversation = find_or_create_conversation(pinfo);
+ djiuav_info = (djiuav_conv_info_t *)conversation_get_proto_data(conversation, proto_djiuav);
+ if (!djiuav_info) {
+ djiuav_info = wmem_new(wmem_file_scope(), djiuav_conv_info_t);
+ djiuav_info->pdus=wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
+
+ conversation_add_proto_data(conversation, proto_djiuav, djiuav_info);
+ }
+ if (!pinfo->fd->visited) {
+ if (is_cmd) {
+ djiuav_trans=wmem_new(wmem_file_scope(), djiuav_transaction_t);
+ djiuav_trans->request_frame=pinfo->num;
+ djiuav_trans->reply_frame=0;
+ djiuav_trans->request_time=pinfo->abs_ts;
+ djiuav_trans->seqno=seq_no;
+ djiuav_trans->command=packet_type;
+ wmem_map_insert(djiuav_info->pdus, GUINT_TO_POINTER((guint)seq_no), (void *)djiuav_trans);
+ } else {
+ djiuav_trans=(djiuav_transaction_t *)wmem_map_lookup(djiuav_info->pdus, GUINT_TO_POINTER((guint)seq_no));
+ if (djiuav_trans) {
+ /* Special case: djiuav seems to send 0x24 replies with seqno 0 and without a request */
+ if (djiuav_trans->reply_frame == 0)
+ djiuav_trans->reply_frame=pinfo->num;
+ }
+ }
+ } else {
+ djiuav_trans=(djiuav_transaction_t *)wmem_map_lookup(djiuav_info->pdus, GUINT_TO_POINTER((guint)seq_no));
+ }
+
+ /* djiuav_trans may be 0 in case it's a reply without a matching request */
+
+ if (djiuav_tree && djiuav_trans) {
+ if (is_cmd) {
+ if (djiuav_trans->reply_frame) {
+ proto_item *it;
+
+ it = proto_tree_add_uint(djiuav_tree, hf_djiuav_response_in,
+ tvb, 0, 0, djiuav_trans->reply_frame);
+ proto_item_set_generated(it);
+ }
+ } else {
+ if (djiuav_trans->request_frame) {
+ proto_item *it;
+ nstime_t ns;
+
+ it = proto_tree_add_uint(djiuav_tree, hf_djiuav_response_to,
+ tvb, 0, 0, djiuav_trans->request_frame);
+ proto_item_set_generated(it);
+
+ nstime_delta(&ns, &pinfo->abs_ts, &djiuav_trans->request_time);
+ it = proto_tree_add_time(djiuav_tree, hf_djiuav_response_time, tvb, 0, 0, &ns);
+ proto_item_set_generated(it);
+ }
+ }
+ }
+}
+
+static int
+dissect_djiuav_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
+{
+ proto_item *ti;
+ proto_tree *djiuav_tree = NULL;
+ guint32 offset = 0;
+ guint32 pdu_length;
+ guint8 packet_type;
+ gboolean is_cmd;
+
+ is_cmd = (pinfo->match_uint == pinfo->destport);
+ packet_type = tvb_get_guint8(tvb, 6);
+
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_SHORT_NAME);
+ col_add_str(pinfo->cinfo, COL_INFO, is_cmd?"C: ":"R: ");
+ col_append_str(pinfo->cinfo, COL_INFO, val_to_str(packet_type,
+ djiuav_pdu_type, "Type 0x%02x"));
+
+ ti = proto_tree_add_item(tree, proto_djiuav, tvb, offset, -1, ENC_NA);
+ djiuav_tree = proto_item_add_subtree(ti, ett_djiuav);
+
+ request_response_handling(tvb, pinfo, djiuav_tree, offset);
+
+ if (tree) {
+ proto_tree_add_item(djiuav_tree, hf_djiuav_magic, tvb, offset, 2,
+ ENC_BIG_ENDIAN);
+ offset += 2;
+
+ pdu_length = tvb_get_guint8(tvb, offset);
+ proto_tree_add_item(djiuav_tree, hf_djiuav_length, tvb, offset, 1,
+ ENC_NA);
+ offset += 1;
+
+ proto_tree_add_item(djiuav_tree, hf_djiuav_flags, tvb, offset, 1,
+ ENC_NA);
+ offset += 1;
+
+ proto_tree_add_item(djiuav_tree, hf_djiuav_seqno, tvb, offset, 2,
+ ENC_LITTLE_ENDIAN);
+ offset += 2;
+
+ proto_tree_add_item(djiuav_tree, hf_djiuav_cmd, tvb, offset, 1,
+ ENC_NA);
+ offset += 1;
+
+ if (is_cmd) { /* Command */
+ switch (packet_type) {
+ case 0x20: /* Set time */
+/* FIXME: Properly decode this: year(lo) year(hi) month date hour minute second */
+ proto_tree_add_item(djiuav_tree, hf_djiuav_cmd20_unknown, tvb, offset, 7,
+ ENC_NA);
+ offset += 7;
+ break;
+ default:
+ proto_tree_add_item(djiuav_tree, hf_djiuav_cmdunk, tvb, offset, pdu_length - 8,
+ ENC_NA);
+ offset += (pdu_length - 8);
+ break;
+ }
+ } else { /* Response */
+ switch (packet_type) {
+ default:
+ proto_tree_add_item(djiuav_tree, hf_djiuav_respunk, tvb,
+ offset, pdu_length - 8, ENC_NA);
+ offset += (pdu_length - 8);
+ break;
+ }
+ }
+ if (offset < pdu_length - 1) { /* We guessed wrong about the cmd len */
+ proto_tree_add_item(djiuav_tree, hf_djiuav_extradata, tvb, offset,
+ pdu_length - 1 - offset, ENC_NA);
+ offset += pdu_length - 1 - offset;
+ }
+/* FIXME: calculate XOR and validate transmitted value */
+ proto_tree_add_checksum(djiuav_tree, tvb, offset, hf_djiuav_checksum, -1, NULL, pinfo, 0, ENC_BIG_ENDIAN, PROTO_CHECKSUM_NO_FLAGS);
+ offset += 1;
+
+ }
+ return offset;
+}
+
+static gboolean
+test_djiuav(tvbuff_t *tvb)
+{
+ /* Minimum of 8 bytes, beginning with magic bytes 0x55BB */
+ if ( tvb_captured_length(tvb) < 8 /* Size of a command with empty data is at least 8 */
+ || tvb_get_ntohs(tvb, 0) != 0x55BB
+ ) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* Get the length of the full pdu */
+static guint
+get_djiuav_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
+{
+ return tvb_get_guint8(tvb, offset + 2);
+}
+
+static int
+dissect_djiuav_static(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
+{
+ if ( !test_djiuav(tvb) ) {
+ return 0;
+ }
+ tcp_dissect_pdus(tvb, pinfo, tree, djiuav_desegment, 8,
+ get_djiuav_pdu_len, dissect_djiuav_pdu, data);
+
+ return tvb_captured_length(tvb);
+}
+
+void
+proto_register_djiuav(void)
+{
+ static hf_register_info hf[] = {
+
+ /* DJIUAV header */
+ { &hf_djiuav_magic,
+ { "Protocol Magic", "djiuav.magic", FT_UINT16, BASE_HEX, NULL,
+ 0x0, NULL, HFILL }},
+
+ { &hf_djiuav_length,
+ { "PDU Length", "djiuav.length", FT_UINT8, BASE_HEX, NULL,
+ 0x0, NULL, HFILL }},
+
+ { &hf_djiuav_flags,
+ { "Flags", "djiuav.flags", FT_UINT8, BASE_HEX, NULL,
+ 0x0, NULL, HFILL }},
+
+ { &hf_djiuav_seqno,
+ { "Sequence No", "djiuav.seqno", FT_UINT16, BASE_DEC, NULL,
+ 0x0, NULL, HFILL }},
+
+ { &hf_djiuav_cmd,
+ { "PDU Type", "djiuav.pdutype", FT_UINT8, BASE_HEX, VALS(djiuav_pdu_type),
+ 0x0, NULL, HFILL }},
+
+ { &hf_djiuav_checksum,
+ { "Checksum", "djiuav.checksum", FT_UINT8, BASE_HEX, NULL,
+ 0x0, NULL, HFILL }},
+
+ /* 0x04 */
+#if 0
+ { &hf_djiuav_cmd04_unknown,
+ { "C04 Unknown", "djiuav.cmd04.unknown", FT_UINT8, BASE_HEX, NULL,
+ 0x0, NULL, HFILL }},
+
+ { &hf_djiuav_resp04_unknown,
+ { "R04 Unknown", "djiuav.resp04.unknown", FT_UINT8, BASE_HEX, NULL,
+ 0x0, NULL, HFILL }},
+#endif
+ /* Set time */
+ { &hf_djiuav_cmd20_unknown,
+ { "Time in BCD", "djiuav.cmd04.bcdtime", FT_BYTES, BASE_NONE, NULL,
+ 0x0, NULL, HFILL }},
+#if 0
+ { &hf_djiuav_resp20_unknown,
+ { "R20 Unknown", "djiuav.resp04.unknown", FT_UINT8, BASE_HEX, NULL,
+ 0x0, NULL, HFILL }},
+#endif
+ /* CMD Unknown */
+ { &hf_djiuav_cmdunk,
+ { "C Unknown", "djiuav.cmd.unknown", FT_BYTES, BASE_NONE, NULL,
+ 0x0, NULL, HFILL }},
+
+ /* RESP Unknown */
+ { &hf_djiuav_respunk,
+ { "R Unknown", "djiuav.resp.unknown", FT_BYTES, BASE_NONE, NULL,
+ 0x0, NULL, HFILL }},
+
+ /* Extra Data (unexpected) */
+ { &hf_djiuav_extradata,
+ { "Unexpected", "djiuav.unexpected", FT_BYTES, BASE_NONE, NULL,
+ 0x0, NULL, HFILL }},
+
+ /* Request - Response tracking */
+ { &hf_djiuav_response_in,
+ { "Response In", "djiuav.response_in", FT_FRAMENUM, BASE_NONE, NULL,
+ 0x0, "Matching response in frame", HFILL }},
+
+ { &hf_djiuav_response_to,
+ { "Request In", "djiuav.response_to",
+ FT_FRAMENUM, BASE_NONE, NULL,
+ 0x0, "Matching command in frame", HFILL }},
+
+ { &hf_djiuav_response_time,
+ { "Response Time", "djiuav.response_time",
+ FT_RELATIVE_TIME, BASE_NONE, NULL,
+ 0x0, "Time between Command and matching Response", HFILL }},
+ };
+ static gint *ett[] = {
+ &ett_djiuav,
+ };
+ module_t *djiuav_module;
+
+ proto_djiuav = proto_register_protocol(PROTO_LONG_NAME, PROTO_SHORT_NAME, "djiuav");
+ proto_register_field_array(proto_djiuav, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+
+ /* Preferences */
+ djiuav_module = prefs_register_protocol(proto_djiuav, NULL);
+
+ prefs_register_bool_preference(djiuav_module, "desegment",
+ "Reassemble DJIUAV messages",
+ "Whether DJIUAV should reassemble messages spanning multiple"
+ " TCP segments (required to get useful results)",
+ &djiuav_desegment);
+
+ djiuav_handle = register_dissector("djiuav", dissect_djiuav_static, proto_djiuav);
+}
+
+void
+proto_reg_handoff_djiuav(void)
+{
+ dissector_add_uint_with_preference("tcp.port", PORT_DJIUAV, djiuav_handle);
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */